diff --git a/modules/sogecartenet/__init__.py b/modules/sogecartenet/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..3560aad60381bfbc2e7451d0d0a397a1d8e7fc69
--- /dev/null
+++ b/modules/sogecartenet/__init__.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2015 Budget Insight
+#
+# This file is part of a weboob module.
+#
+# This weboob module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This weboob module 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this weboob module. If not, see .
+
+
+from .module import SogecartenetModule
+
+__all__ = ['SogecartenetModule']
diff --git a/modules/sogecartenet/browser.py b/modules/sogecartenet/browser.py
new file mode 100644
index 0000000000000000000000000000000000000000..064058090f97d48ed84aba1c999d7f7dd1b55a4d
--- /dev/null
+++ b/modules/sogecartenet/browser.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2015 Budget Insight
+#
+# This file is part of a weboob module.
+#
+# This weboob module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This weboob module 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this weboob module. If not, see .
+
+
+from weboob.browser import LoginBrowser, URL, need_login
+
+from .pages import LoginPage, AccountsPage, TransactionsPage, PassModificationPage
+
+
+class SogecartesBrowser(LoginBrowser):
+ BASEURL = 'https://www.sogecartenet.fr/'
+
+ login = URL('/internationalisation/identification', LoginPage)
+ pass_modification = URL('/internationalisation/./modificationMotPasse.*', PassModificationPage)
+ accounts = URL('/internationalisation/gestionParcCartes', AccountsPage)
+ transactions = URL('/internationalisation/csv/operationsParCarte.*', TransactionsPage)
+
+ def load_state(self, state):
+ pass
+
+ def do_login(self):
+ assert isinstance(self.username, basestring)
+ assert isinstance(self.password, basestring)
+ data = {"USER": self.username,
+ "PWD": self.password[:10],
+ "ACCES": "PE",
+ "LANGUE": "en",
+ "QUEFAIRE": "LOGIN",
+ }
+ self.login.go(data=data)
+
+ @need_login
+ def iter_accounts(self):
+ self.accounts.go()
+ return self.page.iter_accounts()
+
+ @need_login
+ def get_history(self, account):
+ if not account._url:
+ return ([])
+ self.location(account._url)
+ assert self.transactions.is_here()
+ return self.page.get_history()
diff --git a/modules/sogecartenet/module.py b/modules/sogecartenet/module.py
new file mode 100644
index 0000000000000000000000000000000000000000..bb9d9f7a30da3a3ccd0cfb7026b6b0689c3f8422
--- /dev/null
+++ b/modules/sogecartenet/module.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2015 Budget Insight
+#
+# This file is part of a weboob module.
+#
+# This weboob module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This weboob module 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this weboob module. If not, see .
+
+
+from weboob.tools.backend import Module, BackendConfig
+from weboob.capabilities.bank import CapBank, AccountNotFound
+from weboob.capabilities.base import find_object
+from weboob.tools.value import ValueBackendPassword
+
+from .browser import SogecartesBrowser
+
+
+__all__ = ['SogecartenetModule']
+
+
+class SogecartenetModule(Module, CapBank):
+ NAME = 'sogecartenet'
+ DESCRIPTION = u'Sogecarte Net'
+ MAINTAINER = u'Vincent Paredes'
+ EMAIL = 'vparedes@budget-insight.fr'
+ LICENSE = 'LGPLv3+'
+ VERSION = '1.5'
+ CONFIG = BackendConfig(ValueBackendPassword('login', label='Identifiant', masked=False),
+ ValueBackendPassword('password', label='Mot de passe'))
+
+ BROWSER = SogecartesBrowser
+
+ def create_default_browser(self):
+ return self.create_browser(self.config['login'].get(),
+ self.config['password'].get())
+
+ def get_account(self, _id):
+ return find_object(self.browser.iter_accounts(), id=_id, error=AccountNotFound)
+
+ def iter_accounts(self):
+ return self.browser.iter_accounts()
+
+ def iter_history(self, account):
+ return self.browser.get_history(account)
diff --git a/modules/sogecartenet/pages.py b/modules/sogecartenet/pages.py
new file mode 100644
index 0000000000000000000000000000000000000000..04f1bcd6dbb6ce47bf5e00d7839b3e4a70cfe61a
--- /dev/null
+++ b/modules/sogecartenet/pages.py
@@ -0,0 +1,135 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2015 Budget Insight
+#
+# This file is part of a weboob module.
+#
+# This weboob module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This weboob module 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this weboob module. If not, see .
+
+import requests
+
+from weboob.browser.pages import HTMLPage, CsvPage, pagination
+from weboob.exceptions import BrowserIncorrectPassword, BrowserPasswordExpired, NoAccountsException
+from weboob.browser.elements import DictElement, ItemElement, method, TableElement
+from weboob.browser.filters.standard import CleanText, CleanDecimal, Date, Env
+from weboob.browser.filters.html import TableCell
+from weboob.browser.filters.json import Dict
+from weboob.capabilities.bank import Account
+from weboob.tools.capabilities.bank.transactions import FrenchTransaction
+
+__all__ = ['LoginPage', 'AccountsPage', 'TransactionsPage']
+
+
+class PassModificationPage(HTMLPage):
+ def on_load(self):
+ raise BrowserPasswordExpired('New pass needed')
+
+class LoginPage(HTMLPage):
+ pass
+
+class SogeLoggedPage(object):
+ @property
+ def logged(self):
+ if hasattr(self.doc, 'xpath'):
+ return not self.doc.xpath('//input[@value="LOGIN"][@name="QUEFAIRE"]')
+ return True
+
+ def on_load(self):
+ if hasattr(self.doc, 'xpath') and self.doc.xpath('//input[@value="LOGIN"][@name="QUEFAIRE"]'):
+ raise BrowserIncorrectPassword()
+
+class AccountsPage(SogeLoggedPage, HTMLPage):
+ @pagination
+ @method
+ class iter_accounts(TableElement):
+ item_xpath = '//table[@bgcolor="#92ADC2"]//tr'
+ head_xpath = '//table[@bgcolor="#92ADC2"]/tr[1]/td[text()]'
+
+ col_id = 'card iconetriwbeb(2);'
+ col_label = 'name iconetriwbeb(1);'
+
+ def parse(self, el):
+ msg = CleanText('//font[@color="#FF0000"]')(self)
+ if msg and 'NO INFORMATION AVAILABLE.' in msg:
+ raise NoAccountsException()
+
+ def next_page(self):
+ array_page = self.page.doc.xpath('//table[3]')[0]
+ if array_page.xpath('.//a[@href="javascript:fctSuivant();"]'):
+ curr_page = CleanDecimal().filter(array_page.xpath('.//td')[3].text)
+ data = {'PAGE': curr_page, 'QUEFAIRE':'NEXT', 'TRI':'ACC', 'TYPEDETAIL':'C'}
+ return requests.Request("POST", self.page.url, data=data)
+ return
+
+ class item(ItemElement):
+ klass = Account
+
+ obj_id = CleanText(TableCell('id'), replace=[(' ', ''), ('X','')])
+ obj_label = CleanText(TableCell('label'))
+ obj_type = Account.TYPE_CARD
+ obj_currency = u'EUR'
+
+ @property
+ def obj__url(self):
+ a = self.el.xpath('.//a')
+ if a and len(a) > 1:
+ #handling relative path
+ return '%s/%s' % ('/'.join(self.page.url.split('/')[:-1]), a[-1].attrib['href'][2:])
+ return None
+
+ def condition(self):
+ return self.el.xpath('./td[@bgcolor="#FFFFFF"]')
+
+class TransactionsPage(SogeLoggedPage, CsvPage):
+ ENCODING = 'iso_8859_1'
+ HEADER = 1
+ FMTPARAMS = {'delimiter':';'}
+ @method
+ class get_history(DictElement):
+ class item(ItemElement):
+ klass = FrenchTransaction
+
+ obj_rdate = Date(CleanText(Dict('Processing date')))
+ obj_date = Date(CleanText(Dict('Transaction date')))
+ obj_raw = FrenchTransaction.Raw(CleanText(Dict('corporate name')))
+ obj_amount = FrenchTransaction.Amount(CleanText(Dict('charged amt')), replace_dots=False)
+ obj_original_amount = FrenchTransaction.Amount(CleanText(Dict('orig. currency gross amt')), replace_dots=False)
+ obj_original_currency = CleanText(Dict('orig. currency code'))
+ obj_country = CleanText(Dict('country cde'))
+ obj_type = FrenchTransaction.TYPE_CARD
+
+ def condition(self):
+ return False
+
+ def has_data(self):
+ return not Dict().filter(self.el)['processing date'] == u'No data found'
+
+ def check_debit(self):
+ return Dict().filter(self.el)['debit / credit'] == 'D'
+
+ class credit(item):
+ def condition(self):
+ return self.has_data() and not self.check_debit()
+
+ class debit(item):
+
+ obj_amount = FrenchTransaction.Amount(CleanText(Env('amount')), replace_dots=False)
+ obj_original_amount = FrenchTransaction.Amount(CleanText(Env('original_amount')), replace_dots=False)
+
+ def condition(self):
+ if self.has_data() and self.check_debit():
+ self.env['amount'] = "-" + self.el['charged amt']
+ self.env['original_amount'] = "-" + self.el['orig. currency gross amt']
+ return True
+ return False