diff --git a/modules/ideel/__init__.py b/modules/ideel/__init__.py
new file mode 100644
index 00000000..22e4fdd2
--- /dev/null
+++ b/modules/ideel/__init__.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2015 Oleg Plakhotniuk
+#
+# 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 .module import IdeelModule
+
+__all__ = ['IdeelModule']
diff --git a/modules/ideel/browser.py b/modules/ideel/browser.py
new file mode 100644
index 00000000..e00bd862
--- /dev/null
+++ b/modules/ideel/browser.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2015 Oleg Plakhotniuk
+#
+# 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.capabilities.bank.transactions import \
+ AmericanTransaction as AmTr
+from weboob.browser import LoginBrowser, URL, need_login
+from weboob.browser.pages import HTMLPage
+from weboob.capabilities.base import Currency
+from weboob.capabilities.shop import Order, Item, Payment, OrderNotFound
+from weboob.exceptions import BrowserIncorrectPassword
+
+import re
+from decimal import Decimal
+from datetime import datetime
+from itertools import takewhile, count
+
+
+__all__ = ['Ideel']
+
+
+class IdeelPage(HTMLPage):
+ @property
+ def logged(self):
+ return bool(self.doc.xpath('//a[@href="/logout"]'))
+
+
+class LoginPage(IdeelPage):
+ def login(self, username, password):
+ form = self.get_form(xpath='//form[@id="iform"]')
+ form['login'] = username
+ form['password'] = password
+ form.submit()
+
+
+class HistoryPage(IdeelPage):
+ def exists(self):
+ return bool(self.doc.xpath('//table[@id="order_history"]'))
+
+ def iter_orders(self):
+ return (tr.xpath('td[1]/a/text()')[0][1:]
+ for tr in self.doc.xpath('//table[@id="order_history"]/tbody/tr'))
+
+
+class OrderPage(IdeelPage):
+ def exists(self):
+ return bool(self.order_number())
+
+ def order(self):
+ order = Order(id=self.order_number())
+ order.date = self.order_date()
+ order.tax = self.tax()
+ order.shipping = self.shipping()
+ order.discount = self.discount()
+ order.total = self.total()
+ return order
+
+ def items(self):
+ for tr in self.doc.xpath('//table[contains(@class,"items_table")]'
+ '/tr[td[@class="items_desc"]]'):
+ label = tr.xpath('*//div[@class="item_desc"]//span/text()')[0]
+ url = tr.xpath('*//div[@class="item_img"]//@src')[0]
+ onclk = tr.xpath('*//div[@class="item_img"]//@onclick')
+ if onclk:
+ url=re.match(r'window.open\(\'([^\']*)\'.*', onclk[0]).group(1)
+ if url.startswith('/'):
+ url = self.browser.BASEURL + url
+ price = tr.xpath('td[@class="items_price"]/span/text()')[0]
+ qty = tr.xpath('td[@class="items_qty"]//span/text()')[0]
+ price = AmTr.decimal_amount(price) * Decimal(qty)
+ item = Item()
+ item.label = unicode(label)
+ item.url = unicode(url)
+ item.price = price
+ yield item
+
+ def payments(self):
+ # There's no payment information on Ideel, so we'll make one up.
+ p = Payment()
+ p.date = self.order_date()
+ p.method = u'DEFAULT PAYMENT'
+ p.amount = self.total()
+ yield p
+
+ def order_number(self):
+ return next(iter(self.doc.xpath(
+ u'//b[text()="Order Number:"]/../strong/text()')), None)
+
+ def order_date(self):
+ txt = self.doc.xpath('//div[@id="purchase-notice"]/text()')[0]
+ date = re.match(r'.* (\w+ \d+, \d+)$', txt).group(1)
+ return datetime.strptime(date, '%b %d, %Y')
+
+ def tax(self):
+ return AmTr.decimal_amount(self.doc.xpath(
+ '//span[@id="taxes"]/text()')[0])
+
+ def shipping(self):
+ return AmTr.decimal_amount(self.doc.xpath(
+ '//span[@id="shipping_fee"]/text()')[0])
+
+ def discount(self):
+ TAGS = ['coupon_discount_amount', 'promo_discount_amount',
+ 'total_rewards', 'applied_credit']
+ return -sum(AmTr.decimal_amount(x[1:][:-1]) for tag in TAGS
+ for x in self.doc.xpath('//span[@id="%s"]/text()' % tag))
+
+ def total(self):
+ return AmTr.decimal_amount(self.doc.xpath(
+ '//span[@id="total"]/text()')[0])
+
+
+class Ideel(LoginBrowser):
+ BASEURL = 'http://www.ideel.com'
+ login = URL(r'https://www.ideel.com/login$', LoginPage)
+ history = URL(r'/my_account/orders\?page=(?P\d+)$', HistoryPage)
+ order = URL(r'/my_account/orders/(?P\d+)$', OrderPage)
+ unknown = URL(r'/.*$', IdeelPage)
+
+ def get_currency(self):
+ # Ideel uses only U.S. dollars.
+ return Currency.get_currency(u'$')
+
+ @need_login
+ def get_order(self, id_):
+ if self.order.go(order=id_).exists():
+ return self.page.order()
+ raise OrderNotFound()
+
+ @need_login
+ def iter_orders(self):
+ exists = HistoryPage.exists
+ hists = takewhile(exists, (self.history.go(page=i) for i in count(1)))
+ return (self.get_order(x) for h in hists for x in h.iter_orders())
+
+ @need_login
+ def iter_payments(self, order):
+ return self.order.stay_or_go(order=order.id).payments()
+
+ @need_login
+ def iter_items(self, order):
+ return self.order.stay_or_go(order=order.id).items()
+
+ def do_login(self):
+ self.login.stay_or_go().login(self.username, self.password)
+ if not self.page.logged:
+ raise BrowserIncorrectPassword()
diff --git a/modules/ideel/favicon.png b/modules/ideel/favicon.png
new file mode 100644
index 00000000..0b434100
Binary files /dev/null and b/modules/ideel/favicon.png differ
diff --git a/modules/ideel/module.py b/modules/ideel/module.py
new file mode 100644
index 00000000..e446c163
--- /dev/null
+++ b/modules/ideel/module.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2015 Oleg Plakhotniuk
+#
+# 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.capabilities.shop import CapShop
+from weboob.tools.backend import Module, BackendConfig
+from weboob.tools.value import ValueBackendPassword
+
+from .browser import Ideel
+
+__all__ = ['IdeelModule']
+
+class IdeelModule(Module, CapShop):
+ NAME = 'ideel'
+ MAINTAINER = u'Oleg Plakhotniuk'
+ EMAIL = 'olegus8@gmail.com'
+ VERSION = '1.1'
+ LICENSE = 'AGPLv3+'
+ DESCRIPTION = u'Ideel'
+ CONFIG = BackendConfig(
+ ValueBackendPassword('username', label='User name', masked=False),
+ ValueBackendPassword('password', label='Password'))
+ BROWSER = Ideel
+
+ def create_default_browser(self):
+ return self.create_browser(self.config['username'].get(),
+ self.config['password'].get())
+
+ def get_currency(self):
+ return self.browser.get_currency()
+
+ def get_order(self, id_):
+ return self.browser.get_order(id_)
+
+ def iter_orders(self):
+ return self.browser.iter_orders()
+
+ def iter_payments(self, order):
+ return self.browser.iter_payments(order)
+
+ def iter_items(self, order):
+ return self.browser.iter_items(order)
diff --git a/modules/ideel/test.py b/modules/ideel/test.py
new file mode 100644
index 00000000..19884d55
--- /dev/null
+++ b/modules/ideel/test.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2015 Oleg Plakhotniuk
+#
+# 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.test import BackendTest
+
+
+class IdeelTest(BackendTest):
+ MODULE = 'ideel'
+
+ def test_history(self):
+ """
+ Test that at least one item was ordered in the whole history.
+ """
+ b = self.backend
+ items = (i for o in b.iter_orders() for i in b.iter_items(o))
+ item = next(items, None)
+ self.assertNotEqual(item, None)