# -*- coding: utf-8 -*- # Copyright(C) 2010-2011 Nicolas Duhamel # # This file is part of weboob. # # weboob is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # weboob 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. # # You should have received a copy of the GNU Affero General Public License # along with weboob. If not, see . from datetime import datetime from weboob.capabilities.bank import ( TransferError, TransferBankError, Transfer, TransferStep, NotAvailable, Recipient, AccountNotFound, AddRecipientError ) from weboob.capabilities.base import find_object 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 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 .base import MyHTMLPage class CheckTransferError(MyHTMLPage): def on_load(self): MyHTMLPage.on_load(self) error = CleanText(u'//span[@class="app_erreur"] | //p[@class="warning"] | //p[contains(text(), "Votre virement n\'a pas pu être enregistré")]')(self.doc) if error and not u'Votre demande de virement a été enregistrée le' in error: raise TransferBankError(message=error) class TransferChooseAccounts(LoggedPage, MyHTMLPage): def is_inner(self, text): for option in self.doc.xpath('//select[@id="donneesSaisie.idxCompteEmetteur"]/option'): if text == CleanText('.')(option): return True return False @method class iter_recipients(ListElement): def condition(self): return any(self.env['account_id'] in CleanText('.')(option) for option in self.page.doc.xpath('//select[@id="donneesSaisie.idxCompteEmetteur"]/option')) # You're not dreaming, this is real life item_xpath = '//select[@id="caca"]/option' class Item(ItemElement): klass = Recipient def condition(self): return self.el.attrib['value'] != '-1' def validate(self, obj): # Some international external recipients show those infos: # INT - CA - 0815304220511006 - CEGEP CANADA # Skipping those for the moment. return not obj.iban or is_iban_valid(obj.iban) obj_category = Env('category') obj_label = Env('label') obj_id = Env('id') obj_currency = u'EUR' obj_iban = Env('iban') obj_bank_name = Env('bank_name') obj__value = Attr('.', 'value') def obj_enabled_at(self): return datetime.now().replace(microsecond=0) def parse(self, el): if any(s in CleanText('.')(el) for s in ['Avoir disponible', 'Solde']) or self.page.is_inner(CleanText('.')(el)): self.env['category'] = u'Interne' else: self.env['category'] = u'Externe' if self.env['category'] == u'Interne': _id = Regexp(CleanText('.'), '- (.*?) -')(el) if _id == self.env['account_id']: raise SkipItem() try: account = find_object(self.page.browser.get_accounts_list(), id=_id, error=AccountNotFound) self.env['id'] = _id self.env['label'] = account.label self.env['iban'] = account.iban except AccountNotFound: self.env['id'] = Regexp(CleanText('.'), '- (.*?) -')(el).replace(' ', '') self.env['iban'] = NotAvailable label = CleanText('.')(el).split('-') holder = label[-1] if not any(string in label[-1] for string in ['Avoir disponible', 'Solde']) else label[-2] self.env['label'] = '%s %s' % (label[0].strip(), holder.strip()) self.env['bank_name'] = u'La Banque Postale' else: self.env['id'] = self.env['iban'] = Regexp(CleanText('.'), '- (.*?) -')(el).replace(' ', '') self.env['label'] = Regexp(CleanText('.'), '- (.*?) - (.*)', template='\\2')(el).strip() first_part = CleanText('.')(el).split('-')[0].strip() self.env['bank_name'] = u'La Banque Postale' if first_part in ['CCP', 'PEL'] else NotAvailable 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): 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['donneesSaisie.idxCompteReceveur'] = recipient_value form['donneesSaisie.idxCompteEmetteur'] = matched_values[0] form.submit() class CompleteTransfer(LoggedPage, CheckTransferError): def complete_transfer(self, amount, transfer): form = self.get_form(xpath='//form[@method]') form['montant'] = amount if 'commentaire' in form and transfer.label: form['commentaire'] = transfer.label form['dateVirement'] = transfer.exec_date.strftime('%d/%m/%Y') form.submit() class TransferConfirm(LoggedPage, CheckTransferError): def is_here(self): return not CleanText('//p[contains(text(), "Vous pouvez le consulter dans le menu")]')(self.doc) 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 confirm(self): form = self.get_form(id='formID') 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) try: assert account.id in account_txt or ''.join(account.label.split()) == account_txt assert recipient.id in recipient_txt or ''.join(recipient.label.split()) == recipient_txt except AssertionError: raise TransferError('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) transfer = Transfer() transfer.currency = currency transfer.amount = r_amount transfer.account_iban = account.iban transfer.recipient_iban = recipient.iban transfer.account_id = account.id transfer.recipient_id = recipient.id transfer.exec_date = exec_date transfer.label = reason transfer.account_label = account.label transfer.recipient_label = recipient.label transfer.account_balance = account.balance return transfer 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: # TODO handle transfer with sms code. if u'veuillez saisir votre code de validation' in CleanText('//div[@class="bloc Tmargin"]')(self.doc): raise NotImplementedError() transfer_date = Date(Regexp( CleanText('//div[@class="bloc Tmargin"]'), r'suivant \(([\d\/]+)\)', default=NotAvailable ), dayfirst=True, default=NotAvailable)(self.doc) if transfer_date: transfer.exec_date = transfer_date else: transfer.exec_date = Date(Regexp( CleanText('//div[@class="bloc Tmargin"]'), r'Date ([\d\/]+)' ), dayfirst=True)(self.doc) return transfer class CreateRecipient(LoggedPage, MyHTMLPage): def choose_country(self, recipient, is_bp_account): # if this is present, we can't add recipient currently more_security_needed = self.doc.xpath(u'//iframe[@title="Gestion de compte par Internet"]') if more_security_needed: raise AddRecipientError(message=u"Pour activer le service Certicode, nous vous invitons à vous rapprocher de votre Conseiller en Bureau de Poste.") form = self.get_form(name='SaisiePaysBeneficiaireVirement') form['compteLBP'] = str(is_bp_account).lower() form['beneficiaireBean.paysDestination'] = recipient.iban[:2] form.submit() class ValidateCountry(LoggedPage, MyHTMLPage): def populate(self, recipient): form = self.get_form(name='CaracteristiquesBeneficiaireVirement') form['beneficiaireBean.nom'] = recipient.label form['beneficiaireBean.ibans[1].valeur'] = recipient.iban[2:4] form['beneficiaireBean.ibans[2].valeur'] = recipient.iban[4:8] form['beneficiaireBean.ibans[3].valeur'] = recipient.iban[8:12] form['beneficiaireBean.ibans[4].valeur'] = recipient.iban[12:16] form['beneficiaireBean.ibans[5].valeur'] = recipient.iban[16:20] form['beneficiaireBean.ibans[6].valeur'] = recipient.iban[20:24] form['beneficiaireBean.ibans[7].valeur'] = recipient.iban[24:] form['beneficiaireBean.intituleCompte'] = recipient.label form.submit() class ValidateRecipient(LoggedPage, MyHTMLPage): def is_bp_account(self): msg = CleanText('//span[has-class("app_erreur")]')(self.doc) return msg == u'Le n° de compte que vous avez saisi appartient à La Banque Postale, veuillez vérifier votre saisie.' def get_confirm_link(self): return Link('//a[@title="confirmer la creation"]')(self.doc) class ConfirmPage(LoggedPage, MyHTMLPage): def on_load(self): error_msg = CleanText('//h2[contains(text(), "Compte rendu")]/following-sibling::p')(self.doc) if error_msg: raise AddRecipientError(message=error_msg) 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.recipient_form['url'] = form.url class RcptSummary(LoggedPage, MyHTMLPage): pass