Commit a8539b87 authored by Sylvie Ye's avatar Sylvie Ye Committed by Romain Bignon

[ing] add feature: transfer

parent 04e2a3e0
......@@ -19,11 +19,11 @@
from .login import LoginPage
from .accounts_page import AccountsPage, HistoryPage, ComingPage
from .transfer_page import DebitAccountsPage, CreditAccountsPage
from .transfer_page import DebitAccountsPage, CreditAccountsPage, TransferPage
from .profile_page import ProfilePage
__all__ = ['LoginPage', 'AccountsPage',
'HistoryPage', 'ComingPage',
'DebitAccountsPage', 'CreditAccountsPage',
'DebitAccountsPage', 'CreditAccountsPage', 'TransferPage',
'ProfilePage']
......@@ -27,10 +27,21 @@
class INGVirtKeyboard(SimpleVirtualKeyboard):
# from parent
tile_margin = 3
margin = (0, 4, 0, 0)
convert = 'RGB'
# for children
safe_tile_margin = 10
small_img_size = (100, 40)
alter_img_params = {
'radius': 2,
'percent': 135,
'threshold': 3,
'limit_pixel': 160
}
symbols = {
'0': ('117b18365105224c7207d3ec0ce7516f',),
'1': ('112a72c31ebdf0cdafb84e67c6e1f8f2',),
......@@ -50,10 +61,14 @@ def alter_image(self):
self.original_image = self.image
# create miniature of image to get more reliable hash
self.image = self.image.resize((100, 40), resample=Image.BILINEAR)
self.image = self.image.resize(self.small_img_size, resample=Image.BILINEAR)
# See ImageFilter.UnsharpMask from Pillow
self.image = self.image.filter(ImageFilter.UnsharpMask(radius=2, percent=135, threshold=3))
self.image = Image.eval(self.image, lambda px: 0 if px <= 160 else 255)
self.image = self.image.filter(ImageFilter.UnsharpMask(
radius=self.alter_img_params['radius'],
percent=self.alter_img_params['percent'],
threshold=self.alter_img_params['threshold'])
)
self.image = Image.eval(self.image, lambda px: 0 if px <= self.alter_img_params['limit_pixel'] else 255)
def password_tiles_coord(self, password):
# get image original size to get password coord
......@@ -73,7 +88,7 @@ def password_tiles_coord(self, password):
% (digit, self.path))
formatted_password = []
safe_margin = 10
safe_margin = self.safe_tile_margin
for tile in password_tiles:
# default matching_symbol is str(range(cols*rows))
x0 = (int(tile.matching_symbol) % self.cols) * tile_width
......@@ -101,7 +116,7 @@ def get_password_coord(self, img, password):
pin_position = Dict('pinPositions')(self.doc)
image = BytesIO(img)
vk = INGVirtKeyboard(image, 5, 2, browser=self.browser)
password_radom_coords = vk.password_tiles_coord(password)
vk = INGVirtKeyboard(image, cols=5, rows=2, browser=self.browser)
password_random_coords = vk.password_tiles_coord(password)
# pin positions (website side) start at 1, our positions start at 0
return [password_radom_coords[index-1] for index in pin_position]
return [password_random_coords[index-1] for index in pin_position]
......@@ -20,15 +20,46 @@
from __future__ import unicode_literals
from datetime import datetime
from io import BytesIO
from weboob.browser.pages import LoggedPage, JsonPage
from weboob.browser.elements import method, DictElement, ItemElement
from weboob.browser.filters.json import Dict
from weboob.browser.filters.standard import (
Env, Field,
)
from weboob.browser.filters.standard import Env, Field, Date
from weboob.capabilities.bank import Recipient
from .login import INGVirtKeyboard
class TransferINGVirtKeyboard(INGVirtKeyboard):
# from grand parent
tile_margin = 5
margin = None
convert = 'RGB'
# from parent
safe_tile_margin = 50
small_img_size = (125, 50) # original image size is (2420, 950)
alter_img_params = {
'radius': 2,
'percent': 150,
'threshold': 3,
'limit_pixel': 125
}
symbols = {
'0': 'e3e62175aa1a5ef8dc67639194caa880',
'1': '80245727e4e5f123fd64bbb1fa80dde0',
'2': '62cfc40429652190c996db741ac90830',
'3': 'bb2f87d32f688679745fe95ac31b80fd',
'4': 'a4b5e16c64817deb12ca6311cb98e59a',
'5': '56a8f3b4f068f9e2f93c4daa3a53dc17',
'6': 'b50f7e4a375153b9f6b029dc9b0a7e64',
'7': 'd52320c62c6157d0cadbb7a186153628',
'8': 'dd3fb25fc7f0765610b0ffe47da85330',
'9': 'ca55399a5b36da3fedcd1dbb73d72a2f'
}
class DebitAccountsPage(LoggedPage, JsonPage):
def get_debit_accounts_uid(self):
......@@ -59,3 +90,26 @@ def obj_category(self):
if Field('_is_internal_recipient')(self):
return 'Interne'
return 'Externe'
class TransferPage(LoggedPage, JsonPage):
@property
def suggested_date(self):
return Date(Dict('pinValidateResponse/executionSuggestedDate'), dayfirst=True)(self.doc)
def get_password_coord(self, password):
assert Dict('pinValidateResponse', default=None)(self.doc), "Transfer virtualkeyboard position has failed"
pin_position = Dict('pinValidateResponse/pinPositions')(self.doc)
image_url = '/secure/api-v1%s' % Dict('pinValidateResponse/keyPadUrl')(self.doc)
image = BytesIO(self.browser.open(image_url, headers={'Referer': self.browser.absurl('/secure/transfers/new')}).content)
vk = TransferINGVirtKeyboard(image, cols=5, rows=2, browser=self.browser)
password_random_coords = vk.password_tiles_coord(password)
# pin positions (website side) start at 1, our positions start at 0
return [password_random_coords[index-1] for index in pin_position]
@property
def transfer_is_validated(self):
return Dict('acknowledged')(self.doc)
......@@ -26,10 +26,11 @@
from weboob.browser import LoginBrowser, URL
from weboob.exceptions import BrowserIncorrectPassword, BrowserUnavailable, ActionNeeded
from weboob.browser.exceptions import ClientError
from weboob.capabilities.bank import TransferBankError, TransferInvalidAmount
from .api import (
LoginPage, AccountsPage, HistoryPage, ComingPage,
DebitAccountsPage, CreditAccountsPage,
DebitAccountsPage, CreditAccountsPage, TransferPage,
ProfilePage,
)
from .web import StopPage, ActionNeededPage
......@@ -104,6 +105,8 @@ class IngAPIBrowser(LoginBrowser):
# transfer
credit_accounts = URL(r'/secure/api-v1/transfers/debitAccounts/(?P<account_uid>.*)/creditAccounts', CreditAccountsPage)
debit_accounts = URL(r'/secure/api-v1/transfers/debitAccounts', DebitAccountsPage)
init_transfer_page = URL(r'/secure/api-v1/transfers/v2/new/validate', TransferPage)
exec_transfer_page = URL(r'/secure/api-v1/transfers/v2/new/execute/pin', TransferPage)
# profile
informations = URL(r'/secure/api-v1/customer/info', ProfilePage)
......@@ -113,6 +116,7 @@ def __init__(self, *args, **kwargs):
super(IngAPIBrowser, self).__init__(*args, **kwargs)
self.old_browser = IngBrowser(*args, **kwargs)
self.transfer_data = None
def handle_login_error(self, r):
error_page = r.response.json()
......@@ -314,13 +318,55 @@ def iter_recipients(self, account):
for recipient in self.page.iter_recipients(acc_uid=account._uid):
yield recipient
def handle_transfer_errors(self, r):
error_page = r.response.json()
assert 'error' in error_page, "Something went wrong, transfer is not created"
error = error_page['error']
error_msg = error['message']
if error['code'] == 'TRANSFER.INVALID_AMOUNT_MINIMUM':
raise TransferInvalidAmount(message=error_msg)
elif error['code'] == 'INPUT_INVALID' and len(error['values']):
for value in error['values']:
error_msg = '%s %s %s.' % (error_msg, value, error['values'][value])
raise TransferBankError(message=error_msg)
@need_to_be_on_website('api')
@need_login
def init_transfer(self, account, recipient, transfer):
raise NotImplementedError()
data = {
'amount': transfer.amount,
'executionDate': transfer.exec_date.strftime('%Y-%m-%d'),
'keyPadSize': {'width': 3800, 'height': 1520},
'label': transfer.label,
'fromAccount': account._uid,
'toAccount': recipient.id
}
try:
self.init_transfer_page.go(json=data, headers={'Referer': self.absurl('/secure/transfers/new')})
except ClientError as e:
self.handle_transfer_errors(e)
assert self.page.suggested_date == transfer.exec_date, "Transfer date is not valid"
self.transfer_data = data
self.transfer_data.pop('keyPadSize')
self.transfer_data['clickPositions'] = self.page.get_password_coord(self.password)
return transfer
@need_to_be_on_website('api')
@need_login
def execute_transfer(self, transfer):
raise NotImplementedError()
headers = {
'Referer': self.absurl('/secure/transfers/new'),
'Accept': 'application/json, text/plain, */*'
}
self.exec_transfer_page.go(json=self.transfer_data, headers=headers)
assert self.page.transfer_is_validated, "Transfer is not validated"
return transfer
############# CapDocument #############
@need_login
......
......@@ -19,13 +19,15 @@
from __future__ import unicode_literals
from weboob.capabilities.bank import CapBankWealth, CapBankTransfer, Account, AccountNotFound
from decimal import Decimal
from weboob.capabilities.bank import CapBankWealth, CapBankTransfer, Account, AccountNotFound, RecipientNotFound
from weboob.capabilities.bill import (
CapDocument, Bill, Subscription,
SubscriptionNotFound, DocumentNotFound, DocumentTypes,
)
from weboob.capabilities.profile import CapProfile
from weboob.capabilities.base import find_object
from weboob.capabilities.base import find_object, strict_find_object
from weboob.tools.backend import Module, BackendConfig
from weboob.tools.value import ValueBackendPassword, ValueDate
......@@ -99,10 +101,18 @@ def iter_transfer_recipients(self, account):
return self.browser.iter_recipients(account)
def init_transfer(self, transfer, **params):
raise NotImplementedError()
self.logger.info('Going to do a new transfer')
account = strict_find_object(self.iter_accounts(), id=transfer.account_id, error=AccountNotFound)
recipient = strict_find_object(self.iter_transfer_recipients(account), id=transfer.recipient_id, error=RecipientNotFound)
transfer.amount = Decimal(transfer.amount).quantize(Decimal('.01'))
return self.browser.init_transfer(account, recipient, transfer)
def execute_transfer(self, transfer, **params):
raise NotImplementedError()
return self.browser.execute_transfer(transfer)
############# CapDocument #############
def iter_subscription(self):
......
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