pax_global_header 0000666 0000000 0000000 00000000064 13400742151 0014507 g ustar 00root root 0000000 0000000 52 comment=df3fa35a0de0cc17b437809aefbb99010d1acce2
woob-df3fa35a0de0cc17b437809aefbb99010d1acce2-modules-societegenerale-sgpe/ 0000775 0000000 0000000 00000000000 13400742151 0025547 5 ustar 00root root 0000000 0000000 woob-df3fa35a0de0cc17b437809aefbb99010d1acce2-modules-societegenerale-sgpe/modules/ 0000775 0000000 0000000 00000000000 13400742151 0027217 5 ustar 00root root 0000000 0000000 woob-df3fa35a0de0cc17b437809aefbb99010d1acce2-modules-societegenerale-sgpe/modules/societegenerale/ 0000775 0000000 0000000 00000000000 13400742151 0032355 5 ustar 00root root 0000000 0000000 sgpe/ 0000775 0000000 0000000 00000000000 13400742151 0033234 5 ustar 00root root 0000000 0000000 woob-df3fa35a0de0cc17b437809aefbb99010d1acce2-modules-societegenerale-sgpe/modules/societegenerale __init__.py 0000664 0000000 0000000 00000000000 13400742151 0035333 0 ustar 00root root 0000000 0000000 woob-df3fa35a0de0cc17b437809aefbb99010d1acce2-modules-societegenerale-sgpe/modules/societegenerale/sgpe browser.py 0000664 0000000 0000000 00000054000 13400742151 0035270 0 ustar 00root root 0000000 0000000 woob-df3fa35a0de0cc17b437809aefbb99010d1acce2-modules-societegenerale-sgpe/modules/societegenerale/sgpe # -*- coding: utf-8 -*-
# Copyright(C) 2013 Laurent Bachelier
#
# 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 __future__ import unicode_literals
import re
from datetime import date
from weboob.browser.browsers import LoginBrowser, need_login, StatesMixin
from weboob.browser.url import URL
from weboob.browser.exceptions import ClientError
from weboob.exceptions import BrowserIncorrectPassword, ActionNeeded, NoAccountsException
from weboob.capabilities.base import find_object
from weboob.capabilities.bank import (
AccountNotFound, RecipientNotFound, AddRecipientStep, AddRecipientBankError,
Recipient, TransferBankError,
)
from weboob.tools.value import Value
from .pages import (
LoginPage, CardsPage, CardHistoryPage, IncorrectLoginPage,
ProfileProPage, ProfileEntPage, ChangePassPage, SubscriptionPage, InscriptionPage,
ErrorPage,
)
from .json_pages import (
AccountsJsonPage, BalancesJsonPage, HistoryJsonPage, BankStatementPage,
)
from .transfer_pages import (
EasyTransferPage, RecipientsJsonPage, TransferPage, SignTransferPage, TransferDatesPage,
AddRecipientPage, AddRecipientStepPage, ConfirmRecipientPage,
)
__all__ = ['SGProfessionalBrowser', 'SGEnterpriseBrowser']
class SGPEBrowser(LoginBrowser):
login = URL('$', LoginPage)
cards = URL('/Pgn/.+PageID=Cartes&.+', CardsPage)
cards_history = URL('/Pgn/.+PageID=ReleveCarte&.+', CardHistoryPage)
change_pass = URL('/gao/changer-code-secret-expire-saisie.html',
'/gao/changer-code-secret-inscr-saisie.html',
'/gao/inscrire-utilisateur-saisie.html',
'/gao/changer-code-secret-reattr-saisie.html',
'/gae/afficherInscriptionUtilisateur.html',
'/gae/afficherChangementCodeSecretExpire.html',
ChangePassPage)
inscription_page = URL('/icd-web/gax/gax-inscription.html', InscriptionPage)
def check_logged_status(self):
if not self.page or self.login.is_here():
raise BrowserIncorrectPassword()
error = self.page.get_error()
if error:
raise BrowserIncorrectPassword(error)
def do_login(self):
if not self.password.isdigit():
raise BrowserIncorrectPassword('Password must be 6 digits long.')
self.login.stay_or_go()
if self.page.logged:
return
self.session.cookies.set('PILOTE_OOBA', 'true')
try:
self.page.login(self.username, self.password)
except ClientError:
raise BrowserIncorrectPassword()
if self.inscription_page.is_here():
raise ActionNeeded(self.page.get_error())
# force page change
if not self.accounts.is_here():
self.go_accounts()
self.check_logged_status()
def card_history(self, account, coming):
page = 1
while page:
self.location('/Pgn/NavigationServlet?PageID=ReleveCarte&MenuID=%sOPF&Classeur=1&Rib=%s&Carte=%s&Date=%s&PageDetail=%s&Devise=%s' % \
(self.MENUID, account.id, coming['carte'], coming['date'], page, account.currency))
for transaction in self.page.iter_transactions(date=coming['date']):
yield transaction
if self.page.has_next():
page += 1
else:
page = False
@need_login
def get_cb_operations(self, account):
self.location('/Pgn/NavigationServlet?PageID=Cartes&MenuID=%sOPF&Classeur=1&NumeroPage=1&Rib=%s&Devise=%s' % (self.MENUID, account.id, account.currency))
for coming in self.page.get_coming_list():
if coming['date'] == 'Non definie':
# this is a very recent transaction and we don't know his date yet
continue
for tr in self.card_history(account, coming):
yield tr
def iter_investment(self, account):
raise NotImplementedError()
@need_login
def get_profile(self):
return self.profile.stay_or_go().get_profile()
class SGEnterpriseBrowser(SGPEBrowser):
BASEURL = 'https://entreprises.secure.societegenerale.fr'
MENUID = 'BANREL'
CERTHASH = '2231d5ddb97d2950d5e6fc4d986c23be4cd231c31ad530942343a8fdcc44bb99'
accounts = URL('/icd/syd-front/data/syd-comptes-accederDepuisMenu.json', AccountsJsonPage)
intraday_accounts = URL('/icd/syd-front/data/syd-intraday-accederDepuisMenu.json', AccountsJsonPage)
balances = URL('/icd/syd-front/data/syd-comptes-chargerSoldes.json', BalancesJsonPage)
intraday_balances = URL('/icd/syd-front/data/syd-intraday-chargerSoldes.json', BalancesJsonPage)
history = URL('/icd/syd-front/data/syd-comptes-chargerReleve.json',
'/icd/syd-front/data/syd-intraday-chargerDetail.json', HistoryJsonPage)
history_next = URL('/icd/syd-front/data/syd-comptes-chargerProchainLotEcriture.json', HistoryJsonPage)
profile = URL('/gae/afficherModificationMesDonnees.html', ProfileEntPage)
subscription = URL(r'/Pgn/NavigationServlet\?MenuID=BANRELRIE&PageID=ReleveRIE&NumeroPage=1&Origine=Menu', SubscriptionPage)
subscription_form = URL(r'Pgn/NavigationServlet', SubscriptionPage)
def go_accounts(self):
try:
# get standard accounts
self.accounts.go()
except NoAccountsException:
# get intraday accounts
self.intraday_accounts.go()
@need_login
def get_accounts_list(self):
# 'Comptes' are standard accounts on sge website
# 'Opérations du jour' are intraday accounts on sge website
# Standard and Intraday accounts are same accounts with different detail
# User could have standard accounts with no intraday accounts or the contrary
# They also could have both, in that case, retrieve only standard accounts
try:
# get standard accounts
self.accounts.go()
accounts = list(self.page.iter_class_accounts())
self.balances.go()
except NoAccountsException:
# get intraday accounts
self.intraday_accounts.go()
accounts = list(self.page.iter_class_accounts())
self.intraday_balances.go()
for acc in self.page.populate_balances(accounts):
yield acc
@need_login
def iter_history(self, account):
value = self.history.go(data={'cl500_compte': account._id, 'cl200_typeReleve': 'valeur'}).get_value()
for tr in self.history.go(data={'cl500_compte': account._id, 'cl200_typeReleve': value}).iter_history(value=value):
yield tr
for tr in self.location('/icd/syd-front/data/syd-intraday-chargerDetail.json', data={'cl500_compte': account._id}).page.iter_history():
yield tr
@need_login
def iter_subscription(self):
subscriber = self.get_profile()
self.subscription.go()
for sub in self.page.iter_subscription():
sub.subscriber = subscriber.name
account = find_object(self.get_accounts_list(), id=sub.id, error=AccountNotFound)
sub.label = account.label
yield sub
@need_login
def iter_documents(self, subscription):
data = {
'PageID': 'ReleveRIE',
'MenuID': 'BANRELRIE',
'Origine': 'Menu',
'compteSelected': subscription.id,
}
self.subscription_form.go(data=data)
return self.page.iter_documents(sub_id=subscription.id)
class SGProfessionalBrowser(SGEnterpriseBrowser, StatesMixin):
BASEURL = 'https://professionnels.secure.societegenerale.fr'
MENUID = 'SBOREL'
CERTHASH = '9f5232c9b2283814976608bfd5bba9d8030247f44c8493d8d205e574ea75148e'
STATE_DURATION = 5
incorrect_login = URL('/authent.html', IncorrectLoginPage)
profile = URL('/gao/modifier-donnees-perso-saisie.html', ProfileProPage)
transfer_dates = URL('/ord-web/ord//get-dates-execution.json', TransferDatesPage)
easy_transfer = URL('/ord-web/ord//ord-virement-simplifie-emetteur.html', EasyTransferPage)
internal_recipients = URL('/ord-web/ord//ord-virement-simplifie-beneficiaire.html', EasyTransferPage)
external_recipients = URL('/ord-web/ord//ord-liste-compte-beneficiaire-externes.json', RecipientsJsonPage)
init_transfer_page = URL('/ord-web/ord//ord-enregistrer-ordre-simplifie.json', TransferPage)
sign_transfer_page = URL('/ord-web/ord//ord-verifier-habilitation-signature-ordre.json', SignTransferPage)
confirm_transfer = URL('/ord-web/ord//ord-valider-signature-ordre.json', TransferPage)
recipients = URL('/ord-web/ord//ord-gestion-tiers-liste.json', RecipientsJsonPage)
add_recipient = URL('/ord-web/ord//ord-fragment-form-tiers.html\?cl_action=ajout&cl_idTiers=',
AddRecipientPage)
add_recipient_step = URL('/ord-web/ord//ord-tiers-calcul-bic.json',
'/ord-web/ord//ord-preparer-signature-destinataire.json',
AddRecipientStepPage)
confirm_new_recipient = URL('/ord-web/ord//ord-creer-destinataire.json', ConfirmRecipientPage)
bank_statement_menu = URL('/icd/syd-front/data/syd-rce-accederDepuisMenu.json', BankStatementPage)
bank_statement_search = URL('/icd/syd-front/data/syd-rce-lancerRecherche.json', BankStatementPage)
error_page = URL('https://static.societegenerale.fr/pro/erreur.html', ErrorPage)
date_max = None
date_min = None
new_rcpt_token = None
new_rcpt_validate_form = None
need_reload_state = None
__states__ = ['need_reload_state', 'new_rcpt_token', 'new_rcpt_validate_form']
def load_state(self, state):
# reload state only for new recipient feature
if state.get('need_reload_state'):
state.pop('url', None)
self.need_reload_state = None
super(SGProfessionalBrowser, self).load_state(state)
@need_login
def iter_subscription(self):
profile = self.get_profile()
subscriber = profile.name
self.bank_statement_menu.go()
self.date_min, self.date_max = self.page.get_min_max_date()
return self.page.iter_subscription(subscriber=subscriber)
def get_month_by_range(self, end_month, month_range=3, january_limit=False):
begin_month = ((end_month - month_range) % 12) + 1
if january_limit:
if begin_month >=end_month:
return 1
return begin_month
def exceed_date_min(self, month_min, end_month):
if end_month <= month_min:
return True
def advance_month(self, end_month, end_year, month_range=3):
new_end_month = self.get_month_by_range(end_month, month_range)
if new_end_month > end_month:
end_year -= 1
begin_month = self.get_month_by_range(new_end_month, month_range)
begin_year = end_year
if begin_month > new_end_month:
begin_year -= 1
return new_end_month, end_year, begin_month, begin_year
def copy_recipient_obj(self, recipient):
rcpt = Recipient()
rcpt.id = recipient.iban
rcpt.iban = recipient.iban
rcpt.label = recipient.label
rcpt.category = 'Externe'
rcpt.enabled_at = date.today()
return rcpt
@need_login
def new_recipient(self, recipient, **params):
if 'code' in params:
self.validate_rcpt_with_sms(params['code'])
return self.page.rcpt_after_sms(recipient)
self.recipients.go()
step_urls = {
'first_recipient_check': self.absurl('/ord-web/ord//ord-valider-destinataire-avant-maj.json', base=True),
'get_bic': self.absurl('/ord-web/ord//ord-tiers-calcul-bic.json', base=True),
'get_token': self.absurl('/ord-web/ord//ord-preparer-signature-destinataire.json', base=True),
'get_sign_info': self.absurl('/sec/getsigninfo.json', base=True),
'send_otp_to_user': self.absurl('/sec/csa/send.json', base=True),
}
self.add_recipient.go(method='POST', headers={'Content-Type': 'application/json;charset=UTF-8'})
countries = self.page.get_countries()
# first recipient check
data = {
'an_codeAction': 'ajout_tiers',
'an_refSICoordonnee': '',
'an_refSITiers': '',
'cl_iban': recipient.iban,
'cl_raisonSociale': recipient.label,
}
self.location(step_urls['first_recipient_check'], data=data)
# get bic
data = {
'an_activateCMU': 'true',
'an_codePaysBanque': '',
'an_nature': 'C',
'an_numeroCompte': recipient.iban,
'an_topIBAN': 'true',
'cl_adresse': '',
'cl_adresseBanque': '',
'cl_codePays': recipient.iban[:2],
'cl_libellePaysBanque': '',
'cl_libellePaysDestinataire': countries[recipient.iban[:2]],
'cl_nomBanque': '',
'cl_nomRaisonSociale': recipient.label,
'cl_ville': '',
'cl_villeBanque': '',
}
self.location(step_urls['get_bic'], data=data)
bic = self.page.get_response_data()
# get token
data = {
'an_coordonnee_codePaysBanque': '',
'an_coordonnee_nature': 'C',
'an_coordonnee_numeroCompte': recipient.iban,
'an_coordonnee_topConfidentiel': 'false',
'an_coordonnee_topIBAN': 'true',
'an_refSICoordonnee': '',
'an_refSIDestinataire': '',
'cl_adresse': '',
'cl_codePays': recipient.iban[:2],
'cl_coordonnee_adresseBanque': '',
'cl_coordonnee_bic': bic,
'cl_coordonnee_categories_libelle': '',
'cl_coordonnee_categories_refSi': '',
'cl_coordonnee_libellePaysBanque': '',
'cl_coordonnee_nomBanque': '',
'cl_coordonnee_villeBanque': '',
'cl_libellePaysDestinataire': countries[recipient.iban[:2]],
'cl_nomRaisonSociale': recipient.label,
'cl_ville': '',
}
self.location(step_urls['get_token'], data=data)
self.new_rcpt_validate_form = data
payload = self.page.get_response_data()
# get sign info
data = {
'b64_jeton_transaction': payload['jeton'],
'action_level': payload['sensibilite'],
}
self.location(step_urls['get_sign_info'], data=data)
# send otp to user
data = {
'context': payload['jeton'],
'csa_op': 'sign'
}
self.location(step_urls['send_otp_to_user'], data=data)
self.new_rcpt_validate_form.update(data)
rcpt = self.copy_recipient_obj(recipient)
self.need_reload_state = True
raise AddRecipientStep(rcpt, Value('code', label='Veuillez entrer le code reçu par SMS.'))
@need_login
def validate_rcpt_with_sms(self, code):
assert self.new_rcpt_validate_form, 'There should have recipient validate form in states'
self.new_rcpt_validate_form.update({'code': code})
try:
self.confirm_new_recipient.go(data=self.new_rcpt_validate_form)
except ClientError as e:
assert e.response.status_code == 403, \
'Something went wrong in add recipient, response status code is %s' % e.response.status_code
raise AddRecipientBankError(message='Le code entré est incorrect.')
@need_login
def iter_recipients(self, origin_account):
self.easy_transfer.go()
self.page.update_origin_account(origin_account)
if not hasattr(origin_account, '_product_code'):
# check that origin account is updated, if not, this account can't do transfer
return
params = {
'cl_ibanEmetteur': origin_account.iban,
'cl_codeProduit': origin_account._product_code,
'cl_codeSousProduit': origin_account._underproduct_code,
}
self.internal_recipients.go(method='POST', params=params, headers={'Content-Type': 'application/json;charset=UTF-8'})
for internal_rcpt in self.page.iter_internal_recipients():
yield internal_rcpt
data = {
'an_filtreIban': 'true',
'an_filtreIbanSEPA': 'true',
'an_isCredit': 'true',
'an_isDebit': 'false',
'an_rang': 0,
'an_restrictFRMC': 'false',
'cl_codeProduit': origin_account._product_code,
'cl_codeSousProduit': origin_account._underproduct_code,
'n_nbOccurences': '10000',
}
self.external_recipients.go(data=data)
if self.page.is_external_recipients():
assert self.page.is_all_external_recipient(), "Some recipients are missing"
for external_rcpt in self.page.iter_external_recipients():
yield external_rcpt
@need_login
def init_transfer(self, account, recipient, transfer):
self.transfer_dates.go()
if not self.page.is_date_valid(transfer.exec_date):
raise TransferBankError(message="La date d'exécution du virement est invalide. Elle doit correspondre aux horaires et aux dates d'ouvertures d'agence.")
# update account and recipient info
recipient = find_object(self.iter_recipients(account), iban=recipient.iban, error=RecipientNotFound)
data = [
('an_codeAction', 'C'),
('an_referenceSiOrdre', ''),
('cl_compteEmetteur_intitule', account._account_title),
('cl_compteEmetteur_libelle', account.label),
('an_compteEmetteur_iban', account.iban),
('cl_compteEmetteur_ibanFormate', account._formatted_iban),
('an_compteEmetteur_bic', account._bic),
('b64_compteEmetteur_idPrestation', account._id_service),
('an_guichetGestionnaire', account._manage_counter),
('an_codeProduit', account._product_code),
('an_codeSousProduit', account._underproduct_code),
('n_ordreMontantValeur', int(transfer.amount * (10 ** account._decimal_code))),
('n_ordreMontantCodeDecimalisation', account._decimal_code),
('an_ordreMontantCodeDevise', account._currency_code),
('cl_dateExecution', transfer.exec_date.strftime('%d/%m/%Y')),
('cl_ordreLibelle', transfer.label),
('an_beneficiaireCodeAction', 'C'),
('cl_beneficiaireRefSiCoordonnee', recipient._ref),
('cl_beneficiaireCompteLibelle', recipient.label),
('cl_beneficiaireCompteIntitule', recipient._account_title),
('cl_beneficiaireCompteIbanFormate', recipient._formatted_iban),
('an_beneficiaireCompteIban', recipient.iban),
('cl_beneficiaireCompteBic', recipient._bic),
('cl_beneficiaireAdressePays', recipient.iban[:2]),
('an_indicateurIntraAbonnement', 'false'),
('cl_reference', ' '),
('cl_motif', transfer.label),
]
# WARNING: this save transfer information on user account
self.init_transfer_page.go(data=data)
return self.page.handle_response(account, recipient, transfer.amount, transfer.label, transfer.exec_date)
@need_login
def execute_transfer(self, transfer, **params):
assert transfer._b64_id_transfer, 'Transfer token is missing'
# get virtual keyboard
data = {
'b64_idOrdre': transfer._b64_id_transfer
}
self.sign_transfer_page.go(data=data)
data.update(self.page.get_confirm_transfer_data(self.password))
self.confirm_transfer.go(data=data)
self.page.is_transfer_validated()
return transfer
@need_login
def iter_documents(self, subscribtion):
# This quality website can only fetch documents through a form, looking for dates
# with a range of 3 months maximum
m = re.search(r'(\d{2})/(\d{2})/(\d{4})', self.date_max)
end_day = int(m.group(1))
end_month = int(m.group(2))
end_year = int(m.group(3))
month_range = 3
begin_day = 2
begin_month = self.get_month_by_range(end_month)
begin_year = end_year
if begin_month > end_month:
begin_year -= 1
# current month
data = {
'dt10_dateDebut' :'%02d/%02d/%d' % (begin_day, begin_month, begin_year),
'dt10_dateFin': '%02d/%02d/%d' % (end_day, end_month, end_year),
'cl2000_comptes': '["%s"]' % subscribtion.id,
'cl200_typeRecherche': 'ADVANCED',
}
self.bank_statement_search.go(data=data)
for d in self.page.iter_documents():
yield d
# other months
m = re.search(r'(\d{2})/(\d{2})/(\d{4})', self.date_min)
year_min = int(m.group(3))
month_min = int(m.group(2))
day_min = int(m.group(1))
end_day = 1
is_end = False
while not is_end:
end_month, end_year, begin_month, begin_year = self.advance_month(end_month, end_year, month_range)
if year_min == begin_year and self.exceed_date_min(month_min, begin_month):
begin_day = day_min
begin_month = month_min
is_end = True
data = {
'dt10_dateDebut' :'%02d/%02d/%d' % (begin_day, begin_month, begin_year),
'dt10_dateFin': '%02d/%02d/%d' % (end_day, end_month, end_year),
'cl2000_comptes': '["%s"]' % subscribtion.id,
'cl200_typeRecherche': 'ADVANCED',
}
self.bank_statement_search.go(data=data)
for d in self.page.iter_documents():
yield d
json_pages.py 0000664 0000000 0000000 00000022675 13400742151 0035752 0 ustar 00root root 0000000 0000000 woob-df3fa35a0de0cc17b437809aefbb99010d1acce2-modules-societegenerale-sgpe/modules/societegenerale/sgpe # -*- coding: utf-8 -*-
# Copyright(C) 2016 Baptiste Delpey
#
# 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 .
import requests
from datetime import datetime
from weboob.browser.pages import LoggedPage, JsonPage, pagination
from weboob.browser.elements import ItemElement, method, DictElement
from weboob.browser.filters.standard import (
CleanDecimal, CleanText, Date, Format, BrowserURL, Env,
Field,
)
from weboob.browser.filters.json import Dict
from weboob.capabilities.base import Currency
from weboob.capabilities import NotAvailable
from weboob.capabilities.bank import Account
from weboob.capabilities.bill import Document, Subscription
from weboob.exceptions import (
BrowserUnavailable, NoAccountsException, BrowserIncorrectPassword, BrowserPasswordExpired,
)
from weboob.tools.capabilities.bank.iban import is_iban_valid
from weboob.tools.capabilities.bank.transactions import FrenchTransaction
from weboob.tools.compat import quote_plus
from .pages import Transaction
class AccountsJsonPage(LoggedPage, JsonPage):
ENCODING = 'utf-8'
TYPES = {u'COMPTE COURANT': Account.TYPE_CHECKING,
u'COMPTE PERSONNEL': Account.TYPE_CHECKING,
u'CPTE PRO': Account.TYPE_CHECKING,
u'CPTE PERSO': Account.TYPE_CHECKING,
u'CODEVI': Account.TYPE_SAVINGS,
u'CEL': Account.TYPE_SAVINGS,
u'Ldd': Account.TYPE_SAVINGS,
u'Livret': Account.TYPE_SAVINGS,
u'PEL': Account.TYPE_SAVINGS,
u'Plan Epargne': Account.TYPE_SAVINGS,
u'PEA': Account.TYPE_PEA,
u'Prêt': Account.TYPE_LOAN,
}
def on_load(self):
if self.doc['commun']['statut'].lower() == 'nok':
reason = self.doc['commun']['raison']
if reason == 'SYD-COMPTES-UNAUTHORIZED-ACCESS':
raise NoAccountsException("Vous n'avez pas l'autorisation de consulter : {}".format(reason))
elif reason == 'niv_auth_insuff':
raise BrowserIncorrectPassword('Vos identifiants sont incorrects')
elif reason == 'chgt_mdp_oblig':
raise BrowserPasswordExpired('Veuillez renouveler votre mot de passe')
raise BrowserUnavailable(reason)
@method
class iter_class_accounts(DictElement):
item_xpath = 'donnees/classeurs'
class iter_accounts(DictElement):
@property
def item_xpath(self):
if 'intradayComptes' in self.el:
return 'intradayComptes'
return 'comptes'
class item(ItemElement):
klass = Account
obj__id = Dict('id')
obj_number = CleanText(Dict('iban'), replace=[(' ', '')])
obj_iban = Field('number')
obj_label = CleanText(Dict('libelle'))
obj__agency = Dict('agenceGestionnaire')
def obj_id(self):
number = Field('number')(self)
if len(number) == 27:
# id based on iban to match ids in database.
return number[4:-2]
return number
def obj_iban(self):
# for some account that don't have Iban the account number is store under this variable in the Json
number = Field('number')(self)
if not is_iban_valid(number):
return NotAvailable
return number
def obj_type(self):
return self.page.acc_type(Field('label')(self))
def acc_type(self, label):
for wording, acc_type in self.TYPES.items():
if wording.lower() in label.lower():
return acc_type
return Account.TYPE_CHECKING
def get_error(self):
if self.doc['commun']['statut'] == 'nok':
# warning: 'nok' is case sensitive, for wrongpass at least it's 'nok'
# for certain other errors (like no accounts), it's 'NOK'
return self.doc['commun']['raison']
return None
class BalancesJsonPage(LoggedPage, JsonPage):
def on_load(self):
if self.doc['commun']['statut'] == 'NOK':
reason = self.doc['commun']['raison']
if reason == 'SYD-COMPTES-UNAUTHORIZED-ACCESS':
raise NoAccountsException("Vous n'avez pas l'autorisation de consulter : {}".format(reason))
raise BrowserUnavailable(reason)
def populate_balances(self, accounts):
for account in accounts:
acc_dict = self.doc['donnees']['compteSoldesMap'][account._id]
account.balance = CleanDecimal(replace_dots=True).filter(acc_dict.get('soldeComptable', acc_dict.get('soldeInstantane')))
account.currency = Currency.get_currency(acc_dict.get('deviseSoldeComptable', acc_dict.get('deviseSoldeInstantane')))
account.coming = CleanDecimal(replace_dots=True, default=NotAvailable).filter(acc_dict.get('montantOperationJour'))
yield account
class HistoryJsonPage(LoggedPage, JsonPage):
def get_value(self):
if 'NOK' in self.doc['commun']['statut']:
return 'position'
else:
return 'valeur'
@pagination
@method
class iter_history(DictElement):
def __init__(self, *args, **kwargs):
super(DictElement, self).__init__(*args, **kwargs)
self.item_xpath = 'donnees/compte/operations' if not 'Prochain' in self.page.url else 'donnees/ecritures'
def condition(self):
return 'donnees' in self.page.doc
def next_page(self):
d = self.page.doc['donnees']['compte'] if not 'Prochain' in self.page.url else self.page.doc['donnees']
if 'ecrituresRestantes' in d:
next_ope = d['ecrituresRestantes']
next_data = d['sceauEcriture']
else:
next_ope = d['operationsRestantes']
next_data = d['sceauOperation']
if next_ope:
data = {}
data['b64e4000_sceauEcriture'] = next_data
if not 'intraday' in self.page.url:
data['cl200_typeReleve'] = Env('value')(self)
return requests.Request("POST", BrowserURL('history_next')(self), data=data)
class item(ItemElement):
klass = Transaction
obj_rdate = Date(Dict('date', default=None), dayfirst=True, default=NotAvailable)
obj_date = Date(Dict('dVl', default=None), dayfirst=True, default=NotAvailable)
obj__coming = False
# Label is split into l1, l2, l3, l4, l5.
# l5 is needed for transfer label, for example:
# 'l1': "000001 VIR EUROPEEN EMIS NET"
# 'l2': "POUR: XXXXXXXXXXXXX"
# 'l3': "REF: XXXXXXXXXXXXXX"
# 'l4': "REMISE: XXXXXX TRANSFER LABEL"
# 'l5': "MOTIF: TRANSFER LABEL"
obj_raw = Transaction.Raw(Format(
'%s %s %s %s %s',
Dict('l1'),
Dict('l2'),
Dict('l3'),
Dict('l4'),
Dict('l5'),
))
# keep the 3 first rows for transaction label
obj_label = Transaction.Raw(Format(
'%s %s %s',
Dict('l1'),
Dict('l2'),
Dict('l3'),
))
def obj_amount(self):
return CleanDecimal(Dict('c', default=None), replace_dots=True, default=None)(self) or \
CleanDecimal(Dict('d'), replace_dots=True)(self)
def obj_deleted(self):
return self.obj.type == FrenchTransaction.TYPE_CARD_SUMMARY
class BankStatementPage(LoggedPage, JsonPage):
def get_min_max_date(self):
criterium = self.doc['donnees']['criteres']
return criterium['dateMin'], criterium['dateMax']
@method
class iter_subscription(DictElement):
item_xpath = 'donnees/comptes'
class item(ItemElement):
klass = Subscription
obj_id = Dict('id')
obj_label = Dict('libelle')
obj_subscriber = Env('subscriber')
def iter_documents(self):
account, = self.doc['donnees']['comptes']
statements = account['releves']
for document in statements:
d = Document()
d.date = datetime.strptime(document['dateEdition'], '%d/%m/%Y')
d.label = '%s %s' % (account['libelle'], document['dateEdition'])
d.type = 'document'
d.format = 'pdf'
d.id = '%s_%s' % (account['id'], document['dateEdition'].replace('/', ''))
d.url = '/icd/syd-front/data/syd-rce-telechargerReleve.html?b64e4000_sceau=%s' % quote_plus(document['sceau'])
yield d
pages.py 0000664 0000000 0000000 00000026757 13400742151 0034726 0 ustar 00root root 0000000 0000000 woob-df3fa35a0de0cc17b437809aefbb99010d1acce2-modules-societegenerale-sgpe/modules/societegenerale/sgpe # -*- coding: utf-8 -*-
# Copyright(C) 2013 Laurent Bachelier
#
# 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 __future__ import unicode_literals
from logging import error
import re
from io import BytesIO
from weboob.browser.pages import HTMLPage, LoggedPage
from weboob.browser.elements import ListElement, ItemElement, method
from weboob.browser.filters.standard import (
CleanText, CleanDecimal, Date,
Env, Regexp, Field, Format,
)
from weboob.browser.filters.html import Attr
from weboob.tools.capabilities.bank.transactions import FrenchTransaction
from weboob.capabilities.profile import Profile, Person
from weboob.capabilities.bill import Document, Subscription
from weboob.exceptions import ActionNeeded, BrowserIncorrectPassword, BrowserUnavailable
from weboob.tools.json import json
from weboob.capabilities.base import NotAvailable
from ..captcha import Captcha, TileError
class Transaction(FrenchTransaction):
PATTERNS = [(re.compile(r'^CARTE \w+ RETRAIT DAB.*? (?P
\d{2})/(?P\d{2})( (?P\d+)H(?P\d+))? (?P.*)'),
FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile(r'^CARTE \w+ (?P\d{2})/(?P\d{2})( A (?P\d+)H(?P\d+))? RETRAIT DAB (?P.*)'),
FrenchTransaction.TYPE_WITHDRAWAL),
(re.compile(r'^CARTE \w+ REMBT (?P\d{2})/(?P\d{2})( A (?P\d+)H(?P\d+))? (?P.*)'),
FrenchTransaction.TYPE_PAYBACK),
(re.compile(r'^DEBIT MENSUEL CARTE.*'),
FrenchTransaction.TYPE_CARD_SUMMARY),
(re.compile(r'^CREDIT MENSUEL CARTE.*'),
FrenchTransaction.TYPE_CARD_SUMMARY),
(re.compile(r'^(?PCARTE) \w+ (?P\d{2})/(?P\d{2}) (?P.*)'),
FrenchTransaction.TYPE_CARD),
(re.compile(r'^(?P\d{2})(?P\d{2})/(?P.*?)/?(-[\d,]+)?$'),
FrenchTransaction.TYPE_CARD),
(re.compile(r'^REMISE CB /(?P\d{2})/(?P\d{2}) (?P.*?)/?(-[\d,]+)?$'),
FrenchTransaction.TYPE_CARD),
(re.compile(r'^(?P(COTISATION|PRELEVEMENT|TELEREGLEMENT|TIP|PRLV)) (?P.*)'),
FrenchTransaction.TYPE_ORDER),
(re.compile(r'^(\d+ )?VIR (PERM )?POUR: (.*?) (REF: \d+ )?MOTIF: (?P.*)'),
FrenchTransaction.TYPE_TRANSFER),
(re.compile(r'^(?PVIR(EMEN)?T? \w+) (?P.*)'),
FrenchTransaction.TYPE_TRANSFER),
(re.compile(r'^(CHEQUE) (?P.*)'), FrenchTransaction.TYPE_CHECK),
(re.compile(r'^(FRAIS) (?P.*)'), FrenchTransaction.TYPE_BANK),
(re.compile(r'^(?PECHEANCEPRET)(?P.*)'),
FrenchTransaction.TYPE_LOAN_PAYMENT),
(re.compile(r'^(?PREMISE CHEQUES)(?P.*)'),
FrenchTransaction.TYPE_DEPOSIT),
(re.compile(r'^CARTE RETRAIT (?P.*)'),
FrenchTransaction.TYPE_WITHDRAWAL),
]
_coming = False
class SGPEPage(HTMLPage):
def get_error(self):
err = self.doc.getroot().cssselect('div.ngo_mire_reco_message') \
or self.doc.getroot().cssselect('#nge_zone_centre .nge_cadre_message_utilisateur') \
or self.doc.xpath(u'//div[contains(text(), "Echec de connexion à l\'espace Entreprises")]') \
or self.doc.xpath(u'//div[contains(@class, "waitAuthJetonMsg")]')
if err:
return err[0].text.strip()
class ChangePassPage(SGPEPage):
def on_load(self):
message = (CleanText('//div[@class="ngo_gao_message_intro"]')(self.doc)
or CleanText('//div[@class="ngo_gao_intro"]')(self.doc)
or u'Informations manquantes sur le site Société Générale')
raise ActionNeeded(message)
class LoginPage(SGPEPage):
@property
def logged(self):
return self.doc.xpath('//a[text()="Déconnexion" and @href="/logout"]')
def get_authentication_data(self):
infos_data = self.browser.open('/sec/vk/gen_crypto?estSession=0').text
infos_data = re.match('^_vkCallback\((.*)\);$', infos_data).group(1)
infos = json.loads(infos_data.replace("'", '"'))
url = '/sec/vk/gen_ui?modeClavier=0&cryptogramme=' + infos["crypto"]
img = Captcha(BytesIO(self.browser.open(url).content), infos)
try:
img.build_tiles()
except TileError as err:
error("Error: %s" % err)
if err.tile:
err.tile.display()
return {
'infos': infos,
'img': img,
}
def login(self, login, password):
authentication_data = self.get_authentication_data()
data = {
'user_id': login,
'codsec': authentication_data['img'].get_codes(password[:6]),
'cryptocvcs': authentication_data['infos']['crypto'],
'vk_op': 'auth',
}
self.browser.location(self.browser.absurl('/authent.html'), data=data)
class CardsPage(LoggedPage, SGPEPage):
def get_coming_list(self):
coming_list = []
for a in self.doc.xpath('//a[contains(@onclick, "changeCarte")]'):
m = re.findall("'([^']+)'", Attr(a.xpath('.'), 'onclick')(self))
params = {}
params['carte'] = m[1]
params['date'] = m[2]
coming_list.append(params)
return coming_list
class CardHistoryPage(LoggedPage, SGPEPage):
@method
class iter_transactions(ListElement):
item_xpath = '//table[@id="tab-corps"]//tr'
class item(ItemElement):
klass = Transaction
obj_rdate = Date(CleanText('./td[1]'), dayfirst=True)
obj_date = Date(Env('date'), dayfirst=True, default=NotAvailable)
obj_raw = Transaction.Raw(CleanText('./td[2]'))
obj_type = Transaction.TYPE_DEFERRED_CARD
obj__coming = True
def obj_amount(self):
return CleanDecimal('./td[3]', replace_dots=True, default=NotAvailable)(self) \
or CleanDecimal('./td[2]', replace_dots=True)(self)
def condition(self):
return CleanText('./td[2]')(self)
def has_next(self):
current = None
total = None
for script in self.doc.xpath('//script'):
if script.text is None:
continue
m = re.search('var pageActive\s+= (\d+)', script.text)
if m:
current = int(m.group(1))
m = re.search("var nombrePage\s+= (\d+)", script.text)
if m:
total = int(m.group(1))
if all((current, total)) and current < total:
return True
return False
class ProfileProPage(LoggedPage, SGPEPage):
@method
class get_profile(ItemElement):
klass = Profile
obj_email = Attr('//input[contains(@name, "_email")]', 'value')
def obj_name(self):
civility = CleanText('//td[input[contains(@name, "civilite")][@checked]]/label', default=None)(self) or \
CleanText(u'//tr[td[contains(text(), "Civilité")]]/td[last()]')(self)
firstname = Attr('//input[contains(@name, "_prenom")]', 'value', default=None)(self) or \
CleanText(u'//tr[td[contains(text(), "Prénom")]]/td[last()]')(self)
lastname = Attr('//input[contains(@name, "_nom")]', 'value', default=None)(self) or \
CleanText(u'//tr[td[contains(text(), "Nom")]]/td[last()]')(self)
return "%s %s %s" % (civility, firstname, lastname)
class ProfileEntPage(LoggedPage, SGPEPage):
@method
class get_profile(ItemElement):
klass = Person
obj_email = CleanText('//tr[th[text()="Adresse e-mail"]]/td')
obj_job = CleanText('//tr[th[text()="Fonction dans l\'entreprise"]]/td')
obj_company_name = CleanText('//tr[th[text()="Raison sociale"]]/td')
def obj_phone(self):
return (CleanText('//tr[th[contains(text(), "Téléphone mobile")]]/td')(self)
or CleanText('//tr[th[contains(text(), "Téléphone fixe")]]/td')(self)
or NotAvailable)
def obj_name(self):
civility = CleanText('//tr[th[contains(text(), "Civilité")]]/td')(self)
firstname = CleanText('//tr[th[contains(text(), "Prénom")]]/td')(self)
lastname = CleanText('//tr[th[contains(text(), "Nom")]]/td')(self)
return "%s %s %s" % (civility, firstname, lastname)
class SubscriptionPage(LoggedPage, SGPEPage):
def iter_subscription(self):
for account in self.doc.xpath('//select[@name="compteSelected"]/option'):
s = Subscription()
s.id = CleanText('.', replace=[(' ', '')])(account)
yield s
@method
class iter_documents(ListElement):
item_xpath = '//table[@id="tab-arretes"]/tbody/tr[td[@class="foncel1-grand"]]'
class item(ItemElement):
klass = Document
obj_label = CleanText('./td[1]')
obj_date = Date(Regexp(Field('label'), r'au (\d{4}\-\d{2}\-\d{2})'))
obj_id = Format('%s_%s', Env('sub_id'), CleanText(Regexp(Field('label'), r'au (\d{4}\-\d{2}\-\d{2})'), replace=[('-', '')]))
obj_format = 'pdf'
obj_type = 'document'
obj_url = Format(
'/Pgn/PrintServlet?PageID=ReleveRIE&MenuID=BANRELRIE&urlTypeTransfert=ipdf&REPORTNAME=ReleveInteretElectronique.sgi&numeroRie=%s',
Regexp(Attr('./td[2]/a', 'onclick'), r"impression\('(.*)'\);")
)
class IncorrectLoginPage(SGPEPage):
def on_load(self):
if self.doc.xpath('//div[@class="ngo_mu_message" and contains(text(), "saisies sont incorrectes")]'):
raise BrowserIncorrectPassword(CleanText('//div[@class="ngo_mu_message"]')(self.doc))
class ErrorPage(SGPEPage):
def on_load(self):
if self.doc.xpath('//div[@class="ngo_mu_message" and contains(text(), "momentanément indisponible")]'):
# Warning: it could occurs because of wrongpass, user have to change password
raise BrowserUnavailable(CleanText('//div[@class="ngo_mu_message"]')(self.doc))
class InscriptionPage(SGPEPage):
def get_error(self):
message = CleanText('//head/title')(self.doc)
return message
transfer_pages.py 0000664 0000000 0000000 00000020226 13400742151 0036613 0 ustar 00root root 0000000 0000000 woob-df3fa35a0de0cc17b437809aefbb99010d1acce2-modules-societegenerale-sgpe/modules/societegenerale/sgpe # -*- coding: utf-8 -*-
# Copyright(C) 2018 Sylvie Ye
#
# 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 __future__ import unicode_literals
import json
from datetime import date
from weboob.browser.pages import LoggedPage, HTMLPage, JsonPage
from weboob.browser.elements import method, DictElement, ItemElement
from weboob.browser.filters.standard import CleanText, CleanDecimal
from weboob.browser.filters.html import Attr
from weboob.browser.filters.json import Dict
from weboob.browser.filters.standard import Date, Eval
from weboob.capabilities.bank import Recipient, Transfer
from .pages import LoginPage
class ErrorCheckedJsonPage(JsonPage):
def on_load(self):
assert Dict('commun/statut')(self.doc) == 'ok', \
'Something went wrong: %s' % Dict('commun/raison')(self.doc)
class RecipientsJsonPage(LoggedPage, ErrorCheckedJsonPage):
def is_external_recipients(self):
return Dict('donnees/items')(self.doc)
def is_all_external_recipient(self):
return (
Dict('donnees/nbTotalDestinataires')(self.doc) == len(self.doc['donnees']['items'])
and not Dict('donnees/moreItems')(self.doc)
)
@method
class iter_external_recipients(DictElement):
item_xpath = 'donnees/items'
class item(ItemElement):
klass = Recipient
def condition(self):
return Dict('coordonnee/0/natureTypee')(self) == 'CREDIT'
obj_category = 'Externe'
obj_id = Dict('coordonnee/0/refSICoordonnee')
obj_iban = Dict('coordonnee/0/numeroCompte')
obj_label = obj__account_title = Dict('nomRaisonSociale')
obj_enabled_at = date.today()
obj__formatted_iban = Dict('coordonnee/0/numeroCompteFormate')
obj__bic = Dict('coordonnee/0/BIC')
obj__ref = Dict('coordonnee/0/refSICoordonnee')
class TransferDatesPage(LoggedPage, ErrorCheckedJsonPage):
def is_date_valid(self, exec_date):
transfer_dates_list = Dict('donnees/listeDatesExecution')(self.doc)
assert transfer_dates_list
return exec_date.strftime('%d/%m/%Y') in transfer_dates_list
class EasyTransferPage(LoggedPage, HTMLPage):
def update_origin_account(self, origin_account):
for account in self.doc.xpath('//ul[@id="idCptFrom"]//li'):
# get all account data
data = Attr('.', 'data-comptecomplet')(account)
json_data = json.loads(data.replace('"', '"'))
if (
origin_account.label == CleanText().filter(json_data['libelleCompte'])
and origin_account.iban == json_data['ibanCompte']
):
origin_account._currency_code = json_data['codeDevise']
origin_account._formatted_iban = json_data['ibanFormateCompte']
origin_account._min_amount = json_data['montantMin']
origin_account._max_amount = json_data['montantMax']
origin_account._decimal_code = json_data['codeDecimal']
origin_account._manage_counter = json_data['guichetGestionnaire']
origin_account._account_title = json_data['intituleCompte']
origin_account._bic = json_data['bicCompte']
origin_account._id_service = json_data['idPrestation']
origin_account._product_code = json_data['codeProduit']
origin_account._underproduct_code = json_data['codeSousProduit']
break
else:
# some accounts are not able to do transfer
self.logger.warning('Account %s not found on transfer page', origin_account.label)
def iter_internal_recipients(self):
if self.doc.xpath('//ul[@id="idCmptToInterne"]'):
for account in self.doc.xpath('//ul[@id="idCmptToInterne"]/li'):
data = Attr('.', 'data-comptecomplet')(account)
json_data = json.loads(data.replace('"', '"'))
rcpt = Recipient()
rcpt.category = 'Interne'
rcpt.id = rcpt.iban = json_data['ibanCompte']
rcpt.label = json_data['libelleCompte']
rcpt.enabled_at = date.today()
rcpt._formatted_iban = json_data['ibanFormateCompte']
rcpt._account_title = json_data['intituleCompte']
rcpt._bic = json_data['bicCompte']
rcpt._ref = ''
yield rcpt
class TransferPage(LoggedPage, ErrorCheckedJsonPage):
def handle_response(self, origin, recipient, amount, reason, exec_date):
account_data = Dict('donnees/detailOrdre/compteEmetteur')(self.doc)
recipient_data = Dict('donnees/listOperations/0/compteBeneficiaire')(self.doc)
transfer_data = Dict('donnees/detailOrdre')(self.doc)
transfer = Transfer()
transfer._b64_id_transfer = Dict('idOrdre')(transfer_data)
transfer.account_id = origin.id
transfer.account_label = Dict('libelleCompte')(account_data)
transfer.account_iban = Dict('ibanCompte')(account_data)
transfer.account_balance = origin.balance
transfer.recipient_id = recipient.id
transfer.recipient_label = Dict('libelleCompte')(recipient_data)
transfer.recipient_iban = Dict('ibanCompte')(recipient_data)
transfer.currency = Dict('montantTotalOrdre/codeDevise')(transfer_data)
transfer.amount = CleanDecimal(Eval(
lambda x, y: x * (10 ** -y),
Dict('montantTotalOrdre/valeurMontant'),
Dict('montantTotalOrdre/codeDecimalisation')
))(transfer_data)
transfer.exec_date = Date(Dict('dateExecution'), dayfirst=True)(transfer_data)
transfer.label = Dict('libelleClientOrdre')(transfer_data)
return transfer
def is_transfer_validated(self):
return Dict('donnees/statutOrdre')(self.doc) not in ('rejete', 'a_signer', )
class SignTransferPage(LoggedPage, LoginPage):
def get_token(self):
result_page = json.loads(self.content)
assert result_page['commun']['statut'] == 'ok', 'Something went wrong: %s' % result_page['commun']['raison']
return result_page['donnees']['jeton']
def get_confirm_transfer_data(self, password):
token = self.get_token()
authentication_data = self.get_authentication_data()
return {
'codsec': authentication_data['img'].get_codes(password[:6]),
'cryptocvcs': authentication_data['infos']['crypto'],
'vk_op': 'sign',
'context': token,
}
class AddRecipientPage(LoggedPage, HTMLPage):
def get_countries(self):
countries = {}
for country in self.doc.xpath('//div[@id="div-pays-tiers"]//li[not(@data-codepays="")]'):
countries.update({
Attr('.', 'data-codepays')(country): Attr('.', 'data-libellepays')(country)
})
return countries
class AddRecipientStepPage(LoggedPage, ErrorCheckedJsonPage):
def get_response_data(self):
return self.doc['donnees']
class ConfirmRecipientPage(LoggedPage, ErrorCheckedJsonPage):
def rcpt_after_sms(self, recipient):
rcpt_data = self.doc['donnees']
assert recipient.label == Dict('nomRaisonSociale')(rcpt_data)
assert recipient.iban == Dict('coordonnee/0/numeroCompte')(rcpt_data)
rcpt = Recipient()
rcpt.id = Dict('coordonnee/0/refSICoordonnee')(rcpt_data)
rcpt.iban = Dict('coordonnee/0/numeroCompte')(rcpt_data)
rcpt.label = Dict('nomRaisonSociale')(rcpt_data)
rcpt.category = u'Externe'
rcpt.enabled_at = date.today()
return rcpt