diff --git a/modules/lcl/backend.py b/modules/lcl/backend.py
index 2c52bb58..5bbc9e10 100644
--- a/modules/lcl/backend.py
+++ b/modules/lcl/backend.py
@@ -25,6 +25,7 @@ from weboob.tools.backend import BaseBackend, BackendConfig
from weboob.tools.value import ValueBackendPassword, Value
from .browser import LCLBrowser
+from .enterprise.browser import LCLEnterpriseBrowser
__all__ = ['LCLBackend']
@@ -37,15 +38,33 @@ class LCLBackend(BaseBackend, ICapBank):
VERSION = '0.g'
DESCRIPTION = u'Le Crédit Lyonnais French bank website'
LICENSE = 'AGPLv3+'
- CONFIG = BackendConfig(ValueBackendPassword('login', label='Account ID', regexp='^\d+\w$', masked=False),
- ValueBackendPassword('password', label='Password of account', regexp='^\d{6}$'),
- Value('agency', label='Agency code (deprecated)', regexp='^(\d{3,4}|)$', default=''))
+ CONFIG = BackendConfig(ValueBackendPassword('login', label='Account ID', masked=False),
+ ValueBackendPassword('password', label='Password of account'),
+ Value('agency', label='Agency code (deprecated)', regexp='^(\d{3,4}|)$', default=''),
+ Value('website', label='Website to use', default='par',
+ choices={'par': 'Particuliers',
+ 'ent': 'Entreprises'}))
BROWSER = LCLBrowser
def create_default_browser(self):
- return self.create_browser(self.config['agency'].get(),
- self.config['login'].get(),
- self.config['password'].get())
+ website = self.config['website'].get()
+ if website == 'ent':
+ self.BROWSER = LCLEnterpriseBrowser
+ return self.create_browser(self.config['login'].get(),
+ self.config['password'].get())
+ else:
+ self.BROWSER = LCLBrowser
+ return self.create_browser(self.config['agency'].get(),
+ self.config['login'].get(),
+ self.config['password'].get())
+
+ def deinit(self):
+ try:
+ deinit = self.browser.deinit
+ except AttributeError:
+ pass
+ else:
+ deinit()
def iter_accounts(self):
for account in self.browser.get_accounts_list():
@@ -60,6 +79,9 @@ class LCLBackend(BaseBackend, ICapBank):
raise AccountNotFound()
def iter_coming(self, account):
+ if self.BROWSER != LCLBrowser:
+ raise NotImplementedError
+
with self.browser:
transactions = list(self.browser.get_cb_operations(account))
transactions.sort(key=lambda tr: tr.rdate, reverse=True)
diff --git a/modules/lcl/enterprise/__init__.py b/modules/lcl/enterprise/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/modules/lcl/enterprise/browser.py b/modules/lcl/enterprise/browser.py
new file mode 100644
index 00000000..e489fff8
--- /dev/null
+++ b/modules/lcl/enterprise/browser.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2010-2013 Romain Bignon, Pierre Mazière, Noé Rubinstein
+#
+# 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 weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword
+
+from .pages import HomePage, MessagesPage, LogoutPage, LogoutOkPage, \
+ AlreadyConnectedPage, ExpiredPage, MovementsPage, RootPage
+
+
+__all__ = ['LCLEnterpriseBrowser']
+
+
+class LCLEnterpriseBrowser(BaseBrowser):
+ PROTOCOL = 'https'
+ DOMAIN = 'entreprises.secure.lcl.fr'
+ #TODO: CERTHASH = ['ddfafa91c3e4dba2e6730df723ab5559ae55db351307ea1190d09bd025f74cce', '430814d3713cf2556e74749335e9d7ad8bb2a9350a1969ee539d1e9e9492a59a']
+ ENCODING = 'utf-8'
+ USER_AGENT = BaseBrowser.USER_AGENTS['wget']
+
+ PAGES_REV = {
+ LogoutPage: 'https://entreprises.secure.lcl.fr/outil/IQEN/Authentication/logout',
+ LogoutOkPage: 'https://entreprises.secure.lcl.fr/outil/IQEN/Authentication/logoutOk',
+ HomePage: 'https://entreprises.secure.lcl.fr/indexcle.html',
+ MessagesPage: 'https://entreprises.secure.lcl.fr/outil/IQEN/Bureau/mesMessages',
+ MovementsPage: 'https://entreprises.secure.lcl.fr/outil/IQMT/mvt.Synthese/syntheseMouvementPerso',
+ }
+ PAGES = {
+ PAGES_REV[HomePage]: HomePage,
+ PAGES_REV[LogoutPage]: LogoutPage,
+ PAGES_REV[LogoutOkPage]: LogoutOkPage,
+ PAGES_REV[MessagesPage]: MessagesPage,
+ PAGES_REV[MovementsPage]: MovementsPage,
+ 'https://entreprises.secure.lcl.fr/': RootPage,
+ 'https://entreprises.secure.lcl.fr/outil/IQEN/Authentication/dejaConnecte': AlreadyConnectedPage,
+ 'https://entreprises.secure.lcl.fr/outil/IQEN/Authentication/sessionExpiree': ExpiredPage,
+ }
+
+ def __init__(self, *args, **kwargs):
+ BaseBrowser.__init__(self, *args, **kwargs)
+ self._logged = False
+
+ def deinit(self):
+ if self._logged:
+ self.logout()
+
+ def is_logged(self):
+ ID_XPATH = '//div[@id="headerIdentite"]'
+ self._logged = bool(self.page.document.xpath(ID_XPATH))
+ return self._logged
+
+ def login(self):
+ assert isinstance(self.username, basestring)
+ assert isinstance(self.password, basestring)
+
+ if not self.is_on_page(HomePage):
+ self.location('/indexcle.html', no_login=True)
+
+ self.page.login(self.username, self.password)
+
+ if self.is_on_page(AlreadyConnectedPage):
+ raise BrowserIncorrectPassword("Another session is already open. Please try again later.")
+ if not self.is_logged():
+ raise BrowserIncorrectPassword("invalid login/password.\nIf you did not change anything, be sure to check for password renewal request\non the original web site.\nAutomatic renewal will be implemented later.")
+
+ def logout(self):
+ self.location(self.PAGES_REV[LogoutPage], no_login=True)
+ self.location(self.PAGES_REV[LogoutOkPage], no_login=True)
+ assert self.is_on_page(LogoutOkPage)
+
+ def get_accounts_list(self):
+ return [self.get_account()]
+
+ def get_account(self, id=None):
+ if not self.is_on_page(MovementsPage):
+ self.location(self.PAGES_REV[MovementsPage])
+
+ return self.page.get_account()
+
+ def get_history(self, account):
+ if not self.is_on_page(MovementsPage):
+ self.location(self.PAGES_REV[MovementsPage])
+
+ for tr in self.page.get_operations():
+ yield tr
diff --git a/modules/lcl/enterprise/pages.py b/modules/lcl/enterprise/pages.py
new file mode 100644
index 00000000..32f07c55
--- /dev/null
+++ b/modules/lcl/enterprise/pages.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2010-2013 Romain Bignon, Pierre Mazière, Noé Rubinstein
+#
+# 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 decimal import Decimal
+
+from weboob.capabilities.bank import Account
+from weboob.tools.browser import BasePage
+from weboob.tools.capabilities.bank.transactions import FrenchTransaction
+
+from ..pages import Transaction
+
+
+class RootPage(BasePage):
+ pass
+
+
+class LogoutPage(BasePage):
+ pass
+
+
+class LogoutOkPage(BasePage):
+ pass
+
+
+class MessagesPage(BasePage):
+ pass
+
+
+class AlreadyConnectedPage(BasePage):
+ pass
+
+
+class ExpiredPage(BasePage):
+ pass
+
+
+class MovementsPage(BasePage):
+ def get_account(self):
+ LABEL_XPATH = '//*[@id="perimetreMandatEnfantLib"]'
+ BALANCE_XPATH = '//div[contains(text(),"Solde comptable :")]/strong'
+
+ account = Account()
+
+ account.id = 0
+ account.label = self.document.xpath(LABEL_XPATH)[0] \
+ .text_content().strip()
+ balance_txt = self.document.xpath(BALANCE_XPATH)[0] \
+ .text_content().strip()
+ account.balance = Decimal(FrenchTransaction.clean_amount(balance_txt))
+ account.currency = Account.get_currency(balance_txt)
+
+ return account
+
+ def get_operations(self):
+ LINE_XPATH = '//table[@id="listeEffets"]/tbody/tr'
+
+ for line in self.document.xpath(LINE_XPATH):
+ _id = line.xpath('./@id')[0]
+ tds = line.xpath('./td')
+
+ [date, vdate, raw, debit, credit] = [td.text_content() for td in tds]
+
+ operation = Transaction(_id)
+ operation.parse(date=date, raw=raw)
+ operation.set_amount(credit, debit)
+
+ yield operation
+
+
+class HomePage(BasePage):
+ def login(self, login, passwd):
+ p = lambda f: f.attrs.get('id') == "form_autoComplete"
+ self.browser.select_form(predicate=p)
+ self.browser["Ident_identifiant_"] = login.encode('utf-8')
+ self.browser["Ident_password_"] = passwd.encode('utf-8')
+ self.browser.submit(nologin=True)
diff --git a/modules/lcl/pages.py b/modules/lcl/pages.py
index dc997828..f771c8c5 100644
--- a/modules/lcl/pages.py
+++ b/modules/lcl/pages.py
@@ -226,10 +226,10 @@ class Transaction(FrenchTransaction):
(re.compile('^(?P(PRELEVEMENT|TELEREGLEMENT|TIP)) (?P.*)'),
FrenchTransaction.TYPE_ORDER),
(re.compile('^(?PECHEANCEPRET)(?P.*)'), FrenchTransaction.TYPE_LOAN_PAYMENT),
- (re.compile('^(?PVIR(EMEN)?T? ((RECU|FAVEUR) TIERS|SEPA RECU)?)( /FRM)?(?P.*)'),
+ (re.compile('^(?PVIR(EM(EN)?)?T? ((RECU|FAVEUR) TIERS|SEPA RECU)?)( /FRM)?(?P.*)'),
FrenchTransaction.TYPE_TRANSFER),
(re.compile('^(?PREMBOURST)(?P.*)'), FrenchTransaction.TYPE_PAYBACK),
- (re.compile('^(?PCOMMISSIONS)(?P.*)'), FrenchTransaction.TYPE_BANK),
+ (re.compile('^(?PCOM(MISSIONS?)?)(?P.*)'), FrenchTransaction.TYPE_BANK),
(re.compile('^(?P(?PREMUNERATION).*)'), FrenchTransaction.TYPE_BANK),
(re.compile('^(?PREM CHQ) (?P.*)'), FrenchTransaction.TYPE_DEPOSIT),
]