mirror of
https://github.com/OCA/bank-statement-import.git
synced 2025-01-20 12:37:43 +02:00
[IMP] account_bank_statement_import_online_transferwise: support SCA
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
# Copyright 2019 Brainbean Apps (https://brainbeanapps.com)
|
# Copyright 2019 Brainbean Apps (https://brainbeanapps.com)
|
||||||
# Copyright 2020 CorporateHub (https://corporatehub.eu)
|
# Copyright 2020-2021 CorporateHub (https://corporatehub.eu)
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Online Bank Statements: TransferWise.com',
|
'name': 'Online Bank Statements: Wise.com (TransferWise.com)',
|
||||||
'version': '12.0.1.0.3',
|
'version': '12.0.1.0.3',
|
||||||
'author':
|
'author':
|
||||||
'CorporateHub, '
|
'CorporateHub, '
|
||||||
@@ -12,11 +12,14 @@
|
|||||||
'website': 'https://github.com/OCA/bank-statement-import/',
|
'website': 'https://github.com/OCA/bank-statement-import/',
|
||||||
'license': 'AGPL-3',
|
'license': 'AGPL-3',
|
||||||
'category': 'Accounting',
|
'category': 'Accounting',
|
||||||
'summary': 'Online bank statements for TransferWise.com',
|
'summary': 'Online bank statements for Wise.com (TransferWise.com)',
|
||||||
'depends': [
|
'depends': [
|
||||||
'account_bank_statement_import_online',
|
'account_bank_statement_import_online',
|
||||||
'web_widget_dropdown_dynamic',
|
'web_widget_dropdown_dynamic',
|
||||||
],
|
],
|
||||||
|
'external_dependencies': {
|
||||||
|
'python': ['cryptography'],
|
||||||
|
},
|
||||||
'data': [
|
'data': [
|
||||||
'views/online_bank_statement_provider.xml',
|
'views/online_bank_statement_provider.xml',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
# Copyright 2019 Brainbean Apps (https://brainbeanapps.com)
|
# Copyright 2019 Brainbean Apps (https://brainbeanapps.com)
|
||||||
# Copyright 2020 CorporateHub (https://corporatehub.eu)
|
# Copyright 2020-2021 CorporateHub (https://corporatehub.eu)
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
|
from base64 import b64encode
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
@@ -10,8 +14,9 @@ import json
|
|||||||
import pytz
|
import pytz
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
from urllib.error import HTTPError
|
||||||
|
|
||||||
from odoo import models, api, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@@ -24,6 +29,14 @@ TRANSFERWISE_API_BASE = 'https://api.transferwise.com'
|
|||||||
class OnlineBankStatementProviderTransferwise(models.Model):
|
class OnlineBankStatementProviderTransferwise(models.Model):
|
||||||
_inherit = 'online.bank.statement.provider'
|
_inherit = 'online.bank.statement.provider'
|
||||||
|
|
||||||
|
# NOTE: This is needed to workaround possible multiple 'origin' fields
|
||||||
|
# present in the same view, resulting in wrong field view configuraion
|
||||||
|
# if more than one is widget="dynamic_dropdown"
|
||||||
|
transferwise_profile = fields.Char(
|
||||||
|
related='origin',
|
||||||
|
readonly=False,
|
||||||
|
)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def values_transferwise_profile(self):
|
def values_transferwise_profile(self):
|
||||||
api_base = self.env.context.get('api_base') or TRANSFERWISE_API_BASE
|
api_base = self.env.context.get('api_base') or TRANSFERWISE_API_BASE
|
||||||
@@ -52,7 +65,7 @@ class OnlineBankStatementProviderTransferwise(models.Model):
|
|||||||
@api.model
|
@api.model
|
||||||
def _get_available_services(self):
|
def _get_available_services(self):
|
||||||
return super()._get_available_services() + [
|
return super()._get_available_services() + [
|
||||||
('transferwise', 'TransferWise.com'),
|
('transferwise', 'Wise.com (TransferWise.com)'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@@ -66,6 +79,13 @@ class OnlineBankStatementProviderTransferwise(models.Model):
|
|||||||
|
|
||||||
api_base = self.api_base or TRANSFERWISE_API_BASE
|
api_base = self.api_base or TRANSFERWISE_API_BASE
|
||||||
api_key = self.password
|
api_key = self.password
|
||||||
|
private_key = self.certificate_private_key
|
||||||
|
if private_key:
|
||||||
|
private_key = serialization.load_pem_private_key(
|
||||||
|
private_key.encode(),
|
||||||
|
password=None,
|
||||||
|
backend=default_backend(),
|
||||||
|
)
|
||||||
currency = (
|
currency = (
|
||||||
self.currency_id or self.company_id.currency_id
|
self.currency_id or self.company_id.currency_id
|
||||||
).name
|
).name
|
||||||
@@ -79,7 +99,9 @@ class OnlineBankStatementProviderTransferwise(models.Model):
|
|||||||
url = api_base + '/v1/borderless-accounts?profileId=%s' % (
|
url = api_base + '/v1/borderless-accounts?profileId=%s' % (
|
||||||
self.origin,
|
self.origin,
|
||||||
)
|
)
|
||||||
data = self._transferwise_retrieve(url, api_key)
|
data = self._transferwise_retrieve(url, api_key, private_key)
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
borderless_account = data[0]['id']
|
borderless_account = data[0]['id']
|
||||||
balance = list(filter(
|
balance = list(filter(
|
||||||
lambda balance: balance['currency'] == currency,
|
lambda balance: balance['currency'] == currency,
|
||||||
@@ -94,15 +116,16 @@ class OnlineBankStatementProviderTransferwise(models.Model):
|
|||||||
# Get starting balance
|
# Get starting balance
|
||||||
starting_balance_timestamp = date_since.isoformat() + 'Z'
|
starting_balance_timestamp = date_since.isoformat() + 'Z'
|
||||||
url = api_base + (
|
url = api_base + (
|
||||||
'/v1/borderless-accounts/%s/statement.json' +
|
'/v3/profiles/%s/borderless-accounts/%s/statement.json' +
|
||||||
'?currency=%s&intervalStart=%s&intervalEnd=%s'
|
'?currency=%s&intervalStart=%s&intervalEnd=%s&type=COMPACT'
|
||||||
) % (
|
) % (
|
||||||
|
self.origin,
|
||||||
borderless_account,
|
borderless_account,
|
||||||
currency,
|
currency,
|
||||||
starting_balance_timestamp,
|
starting_balance_timestamp,
|
||||||
starting_balance_timestamp,
|
starting_balance_timestamp,
|
||||||
)
|
)
|
||||||
data = self._transferwise_retrieve(url, api_key)
|
data = self._transferwise_retrieve(url, api_key, private_key)
|
||||||
balance_start = data['endOfStatementBalance']['value']
|
balance_start = data['endOfStatementBalance']['value']
|
||||||
|
|
||||||
# Get statements, using 469 days (around 1 year 3 month) as step.
|
# Get statements, using 469 days (around 1 year 3 month) as step.
|
||||||
@@ -113,9 +136,10 @@ class OnlineBankStatementProviderTransferwise(models.Model):
|
|||||||
balance_end = None
|
balance_end = None
|
||||||
while interval_start < interval_end:
|
while interval_start < interval_end:
|
||||||
url = api_base + (
|
url = api_base + (
|
||||||
'/v1/borderless-accounts/%s/statement.json' +
|
'/v3/profiles/%s/borderless-accounts/%s/statement.json' +
|
||||||
'?currency=%s&intervalStart=%s&intervalEnd=%s'
|
'?currency=%s&intervalStart=%s&intervalEnd=%s&type=COMPACT'
|
||||||
) % (
|
) % (
|
||||||
|
self.origin,
|
||||||
borderless_account,
|
borderless_account,
|
||||||
currency,
|
currency,
|
||||||
interval_start.isoformat() + 'Z',
|
interval_start.isoformat() + 'Z',
|
||||||
@@ -123,7 +147,7 @@ class OnlineBankStatementProviderTransferwise(models.Model):
|
|||||||
interval_start + interval_step, interval_end
|
interval_start + interval_step, interval_end
|
||||||
).isoformat() + 'Z',
|
).isoformat() + 'Z',
|
||||||
)
|
)
|
||||||
data = self._transferwise_retrieve(url, api_key)
|
data = self._transferwise_retrieve(url, api_key, private_key)
|
||||||
transactions += data['transactions']
|
transactions += data['transactions']
|
||||||
balance_end = data['endOfStatementBalance']['value']
|
balance_end = data['endOfStatementBalance']['value']
|
||||||
interval_start += interval_step
|
interval_start += interval_step
|
||||||
@@ -250,7 +274,7 @@ class OnlineBankStatementProviderTransferwise(models.Model):
|
|||||||
'name': _('Fee for %s') % reference_number,
|
'name': _('Fee for %s') % reference_number,
|
||||||
'amount': str(fees_value),
|
'amount': str(fees_value),
|
||||||
'date': date,
|
'date': date,
|
||||||
'partner_name': 'TransferWise',
|
'partner_name': 'Wise (former TransferWise)',
|
||||||
'unique_import_id': '%s-FEE' % unique_import_id,
|
'unique_import_id': '%s-FEE' % unique_import_id,
|
||||||
'note': _('Transaction fee for %s') % reference_number,
|
'note': _('Transaction fee for %s') % reference_number,
|
||||||
}]
|
}]
|
||||||
@@ -268,20 +292,94 @@ class OnlineBankStatementProviderTransferwise(models.Model):
|
|||||||
return content
|
return content
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _transferwise_retrieve(self, url, api_key):
|
def _transferwise_retrieve(self, url, api_key, private_key=None):
|
||||||
|
try:
|
||||||
with self._transferwise_urlopen(url, api_key) as response:
|
with self._transferwise_urlopen(url, api_key) as response:
|
||||||
content = response.read().decode(
|
content = response.read().decode(
|
||||||
response.headers.get_content_charset() or 'utf-8'
|
response.headers.get_content_charset() or 'utf-8'
|
||||||
)
|
)
|
||||||
|
except HTTPError as e:
|
||||||
|
if e.code != 403 or \
|
||||||
|
e.headers.get('X-2FA-Approval-Result') != 'REJECTED':
|
||||||
|
raise e
|
||||||
|
if not private_key:
|
||||||
|
raise UserError(_(
|
||||||
|
'Strong Customer Authentication is not configured'
|
||||||
|
))
|
||||||
|
one_time_token = e.headers['X-2FA-Approval']
|
||||||
|
signature = private_key.sign(
|
||||||
|
one_time_token.encode(),
|
||||||
|
padding.PKCS1v15(),
|
||||||
|
hashes.SHA256(),
|
||||||
|
)
|
||||||
|
|
||||||
|
with self._transferwise_urlopen(
|
||||||
|
url,
|
||||||
|
api_key,
|
||||||
|
one_time_token,
|
||||||
|
b64encode(signature).decode(),
|
||||||
|
) as response:
|
||||||
|
content = response.read().decode(
|
||||||
|
response.headers.get_content_charset() or 'utf-8'
|
||||||
|
)
|
||||||
|
|
||||||
return self._transferwise_validate(content)
|
return self._transferwise_validate(content)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _transferwise_urlopen(self, url, api_key):
|
def _transferwise_urlopen(self, url, api_key, ott=None, signature=None):
|
||||||
if not api_key:
|
if not api_key:
|
||||||
raise UserError(_('No API key specified!'))
|
raise UserError(_('No API key specified!'))
|
||||||
request = urllib.request.Request(url)
|
request = urllib.request.Request(url)
|
||||||
request.add_header(
|
request.add_header('Authorization', 'Bearer %s' % api_key)
|
||||||
'Authorization',
|
if ott and signature:
|
||||||
'Bearer %s' % api_key
|
request.add_header('X-2FA-Approval', ott)
|
||||||
)
|
request.add_header('X-Signature', signature)
|
||||||
return urllib.request.urlopen(request)
|
return urllib.request.urlopen(request)
|
||||||
|
|
||||||
|
@api.onchange('certificate_private_key', 'service')
|
||||||
|
def _onchange_transferwise_certificate_private_key(self):
|
||||||
|
if self.service != 'transferwise':
|
||||||
|
return
|
||||||
|
|
||||||
|
self.certificate_public_key = False
|
||||||
|
if not self.certificate_private_key:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
private_key = serialization.load_pem_private_key(
|
||||||
|
self.certificate_private_key.encode(),
|
||||||
|
password=None,
|
||||||
|
backend=default_backend(),
|
||||||
|
)
|
||||||
|
self.certificate_public_key = private_key.public_key().public_bytes(
|
||||||
|
serialization.Encoding.PEM,
|
||||||
|
serialization.PublicFormat.PKCS1,
|
||||||
|
).decode()
|
||||||
|
except:
|
||||||
|
_logger.warning('Unable to parse key', exc_info=True)
|
||||||
|
raise UserError(_('Unable to parse key'))
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _transferwise_generate_key(self):
|
||||||
|
self.ensure_one()
|
||||||
|
|
||||||
|
private_key = rsa.generate_private_key(
|
||||||
|
public_exponent=65537,
|
||||||
|
key_size=2048,
|
||||||
|
backend=default_backend(),
|
||||||
|
)
|
||||||
|
self.certificate_private_key = private_key.private_bytes(
|
||||||
|
serialization.Encoding.PEM,
|
||||||
|
serialization.PrivateFormat.TraditionalOpenSSL, # a.k.a. PKCS#1
|
||||||
|
serialization.NoEncryption(),
|
||||||
|
).decode()
|
||||||
|
|
||||||
|
self.certificate_public_key = private_key.public_key().public_bytes(
|
||||||
|
serialization.Encoding.PEM,
|
||||||
|
serialization.PublicFormat.PKCS1,
|
||||||
|
).decode()
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def button_transferwise_generate_key(self):
|
||||||
|
for provider in self:
|
||||||
|
provider._transferwise_generate_key()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ To configure online bank statements provider:
|
|||||||
#. Go to *Invoicing > Configuration > Bank Accounts*
|
#. Go to *Invoicing > Configuration > Bank Accounts*
|
||||||
#. Open bank account to configure and edit it
|
#. Open bank account to configure and edit it
|
||||||
#. Set *Bank Feeds* to *Online*
|
#. Set *Bank Feeds* to *Online*
|
||||||
#. Select *TransferWise.com* as online bank statements provider in
|
#. Select *Wise.com (TransferWise.com)* as online bank statements provider in
|
||||||
*Online Bank Statements (OCA)* section
|
*Online Bank Statements (OCA)* section
|
||||||
#. Save the bank account
|
#. Save the bank account
|
||||||
#. Click on provider and configure provider-specific settings.
|
#. Click on provider and configure provider-specific settings.
|
||||||
@@ -14,7 +14,7 @@ or, alternatively:
|
|||||||
#. Open settings of the corresponding journal account
|
#. Open settings of the corresponding journal account
|
||||||
#. Switch to *Bank Account* tab
|
#. Switch to *Bank Account* tab
|
||||||
#. Set *Bank Feeds* to *Online*
|
#. Set *Bank Feeds* to *Online*
|
||||||
#. Select *TransferWise.com* as online bank statements provider in
|
#. Select *Wise.com (TransferWise.com)* as online bank statements provider in
|
||||||
*Online Bank Statements (OCA)* section
|
*Online Bank Statements (OCA)* section
|
||||||
#. Save the bank account
|
#. Save the bank account
|
||||||
#. Click on provider and configure provider-specific settings.
|
#. Click on provider and configure provider-specific settings.
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
This module provides online bank statements from
|
This module provides online bank statements from
|
||||||
`TransferWise.com <https://transferwise.com/>`__.
|
`Wise.com <https://wise.com/>`__.
|
||||||
|
(formely `TransferWise.com <https://transferwise.com/>`__).
|
||||||
|
|||||||
@@ -4,3 +4,9 @@ To pull historical bank statements:
|
|||||||
#. Select specific bank accounts
|
#. Select specific bank accounts
|
||||||
#. Launch *Actions > Online Bank Statements Pull Wizard*
|
#. Launch *Actions > Online Bank Statements Pull Wizard*
|
||||||
#. Configure date interval and click *Pull*
|
#. Configure date interval and click *Pull*
|
||||||
|
|
||||||
|
To configure Strong Customer Authentication:
|
||||||
|
|
||||||
|
#. Go to provider-specific settings and either press *Generate Key* or paste
|
||||||
|
manually-generate private and public keys
|
||||||
|
#. Navigate to `Wise.com <https://wise.com/public-keys/>`__ and register the public key.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Copyright 2019 Brainbean Apps (https://brainbeanapps.com)
|
# Copyright 2019 Brainbean Apps (https://brainbeanapps.com)
|
||||||
# Copyright 2020 CorporateHub (https://corporatehub.eu)
|
# Copyright 2020-2021 CorporateHub (https://corporatehub.eu)
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -7,6 +7,7 @@ from dateutil.relativedelta import relativedelta
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
import json
|
import json
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
from urllib.error import HTTPError
|
||||||
|
|
||||||
from odoo.tests import common
|
from odoo.tests import common
|
||||||
from odoo import fields
|
from odoo import fields
|
||||||
@@ -19,6 +20,29 @@ _provider_class = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MockedResponse:
|
||||||
|
|
||||||
|
class Headers(dict):
|
||||||
|
def get_content_charset(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __init__(self, data=None, exception=None):
|
||||||
|
self.data = data
|
||||||
|
self.exception = exception
|
||||||
|
self.headers = MockedResponse.Headers()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
if self.exception is not None:
|
||||||
|
raise self.exception()
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
|
||||||
class TestAccountBankAccountStatementImportOnlineTransferwise(
|
class TestAccountBankAccountStatementImportOnlineTransferwise(
|
||||||
common.TransactionCase
|
common.TransactionCase
|
||||||
):
|
):
|
||||||
@@ -47,6 +71,33 @@ class TestAccountBankAccountStatementImportOnlineTransferwise(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
self.response_balance = MockedResponse(data="""[
|
||||||
|
{
|
||||||
|
"id": 42,
|
||||||
|
"balances": [
|
||||||
|
{
|
||||||
|
"currency": "EUR"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]""".encode())
|
||||||
|
self.response_ott = MockedResponse(exception=lambda: HTTPError(
|
||||||
|
'https://wise.com/',
|
||||||
|
403,
|
||||||
|
'403',
|
||||||
|
{
|
||||||
|
'X-2FA-Approval-Result': 'REJECTED',
|
||||||
|
'X-2FA-Approval': '0123456789',
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
self.response_transactions = MockedResponse(data="""{
|
||||||
|
"transactions": [],
|
||||||
|
"endOfStatementBalance": {
|
||||||
|
"value": 42.00,
|
||||||
|
"currency": "EUR"
|
||||||
|
}
|
||||||
|
}""".encode())
|
||||||
|
|
||||||
def test_values_transferwise_profile(self):
|
def test_values_transferwise_profile(self):
|
||||||
mocked_response = json.loads(
|
mocked_response = json.loads(
|
||||||
@@ -86,6 +137,28 @@ class TestAccountBankAccountStatementImportOnlineTransferwise(
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_values_transferwise_profile_no_key(self):
|
||||||
|
values_transferwise_profile = (
|
||||||
|
self.OnlineBankStatementProvider.with_context({
|
||||||
|
'api_base': 'https://example.com',
|
||||||
|
}).values_transferwise_profile()
|
||||||
|
)
|
||||||
|
self.assertEqual(values_transferwise_profile, [])
|
||||||
|
|
||||||
|
def test_values_transferwise_profile_error(self):
|
||||||
|
values_transferwise_profile = []
|
||||||
|
with mock.patch(
|
||||||
|
_provider_class + '._transferwise_retrieve',
|
||||||
|
side_effect=lambda: Exception(),
|
||||||
|
):
|
||||||
|
values_transferwise_profile = (
|
||||||
|
self.OnlineBankStatementProvider.with_context({
|
||||||
|
'api_base': 'https://example.com',
|
||||||
|
'api_key': 'dummy',
|
||||||
|
}).values_transferwise_profile()
|
||||||
|
)
|
||||||
|
self.assertEqual(values_transferwise_profile, [])
|
||||||
|
|
||||||
def test_pull(self):
|
def test_pull(self):
|
||||||
journal = self.AccountJournal.create({
|
journal = self.AccountJournal.create({
|
||||||
'name': 'Bank',
|
'name': 'Bank',
|
||||||
@@ -99,7 +172,7 @@ class TestAccountBankAccountStatementImportOnlineTransferwise(
|
|||||||
provider = journal.online_bank_statement_provider_id
|
provider = journal.online_bank_statement_provider_id
|
||||||
provider.origin = '1234567891'
|
provider.origin = '1234567891'
|
||||||
|
|
||||||
def mock_response(url, api_key):
|
def mock_response(url, api_key, private_key=None):
|
||||||
if '/borderless-accounts?profileId=1234567891' in url:
|
if '/borderless-accounts?profileId=1234567891' in url:
|
||||||
payload = """[
|
payload = """[
|
||||||
{
|
{
|
||||||
@@ -133,6 +206,112 @@ class TestAccountBankAccountStatementImportOnlineTransferwise(
|
|||||||
self.assertEqual(data[1]['balance_start'], 42.0)
|
self.assertEqual(data[1]['balance_start'], 42.0)
|
||||||
self.assertEqual(data[1]['balance_end_real'], 42.0)
|
self.assertEqual(data[1]['balance_end_real'], 42.0)
|
||||||
|
|
||||||
|
def test_pull_no_data(self):
|
||||||
|
journal = self.AccountJournal.create({
|
||||||
|
'name': 'Bank',
|
||||||
|
'type': 'bank',
|
||||||
|
'code': 'BANK',
|
||||||
|
'currency_id': self.currency_eur.id,
|
||||||
|
'bank_statements_source': 'online',
|
||||||
|
'online_bank_statement_provider': 'transferwise',
|
||||||
|
})
|
||||||
|
|
||||||
|
provider = journal.online_bank_statement_provider_id
|
||||||
|
provider.origin = '1234567891'
|
||||||
|
provider.password = 'API_KEY'
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
_provider_class + '._transferwise_retrieve',
|
||||||
|
return_value=[],
|
||||||
|
):
|
||||||
|
data = provider._obtain_statement_data(
|
||||||
|
self.now - relativedelta(hours=1),
|
||||||
|
self.now,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertFalse(data)
|
||||||
|
|
||||||
|
def test_update_public_key(self):
|
||||||
|
journal = self.AccountJournal.create({
|
||||||
|
'name': 'Bank',
|
||||||
|
'type': 'bank',
|
||||||
|
'code': 'BANK',
|
||||||
|
'currency_id': self.currency_eur.id,
|
||||||
|
'bank_statements_source': 'online',
|
||||||
|
'online_bank_statement_provider': 'transferwise',
|
||||||
|
})
|
||||||
|
|
||||||
|
provider = journal.online_bank_statement_provider_id
|
||||||
|
provider.origin = '1234567891'
|
||||||
|
provider.password = 'API_KEY'
|
||||||
|
|
||||||
|
with common.Form(provider) as provider_form:
|
||||||
|
provider_form.certificate_private_key = """
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEAxC7aYWigCwPIB4mfyLpsALYPnqDm3/IC8I/3GdEwfK8eqXoF
|
||||||
|
sU1BHnVytFycBDEObmJ2Acpxe8Dk61FnbWPrrl6rXVnXfIRqfFl94TvgwFsuwG7u
|
||||||
|
8crncD6gPfe1QGkEykHcfBURr74OcSE8590ngNJcKMGvac0cuyZ2/NEszTw7EFJg
|
||||||
|
obpMWjp0m5ItgZ/UNsPLR/D4gFE9vZz7a4+FYQMa9Wbv+xaVxUS6z9rQCJfUQx7N
|
||||||
|
iih4etIvAafbfAnX6rFv8PwPzz+XvexPWWJxnbS4iV1LN2atrPDxqw73g5hc3W88
|
||||||
|
a0V2AVubtxhw9L2VK1VmRb/gnqsZpRXhDPSUIwIDAQABAoIBAQCMvnRLV80hudfC
|
||||||
|
mJh6YEvlgrfX/OVFmpFDVnVXHz2i5dugiHsXBS6HlIjzHlGLrEoHJTo19K/PscZJ
|
||||||
|
kEAcOYg2s5JLSY4PtcvTZDyr3tJSDdiPk8Z2zzOU0kkRy+lLyUv3cqKknlTu+PHR
|
||||||
|
daAFVCLoB4K4dqPKyq0nEuRgYgy7O42SPBY5DHgWYBKqkYGlTu+ImwpDD9unbv3e
|
||||||
|
mwvdcBCp9hAlYAArc1Ip/6aUkZdKxJYgVhovruaH309yuOmBfAEgguhsy3vR18t5
|
||||||
|
IZXbAF3C6iXCQXi8l+S1NUu8XWPLEavldb+ZA2hI2L+NPSBVIYqhI4jDiI7lfs1c
|
||||||
|
HE8BRsRpAoGBAO6BnK3qD8sRvg6JsrBhppoIGsudOdpZ/KVp9ZpYCBJNJmfrkqLR
|
||||||
|
bWx1KF2UjAoYUmaKDTS2GP8JQd7X2n4T5LX8q+7iG9/wzdSWZYZuBOnjvWlNyJu4
|
||||||
|
OiUKX4aEgdvZHiuEIin5xTP98/c5LTZXwM3bq8IrOXEz8LBLLPrTCGRvAoGBANKS
|
||||||
|
i3cn1jtVirJWbvhSIjjqhpfuZN0361FB6j1Aho+7z0WVd4NQjPQqA6cAqnWoa/kj
|
||||||
|
cX0X8Ncu5eHqf6CuW+HsQda3yp3bvCXi1Yc2nKBTHnWtMm721O4ZW6rbaALzBZYW
|
||||||
|
qeJr0m9pNlfCAL0INTcy7IVAtqcCJ/7CEN6Hjm2NAoGAIGSgKArDLFxziLvw9f29
|
||||||
|
R+xT31WyVtKj+r9iaR0Ns5ag4bpgBxcUmodq/RLA1lopTt3vHzqgOHtEZATDGx6O
|
||||||
|
kJ0JqP8ys/6bpgTrMw/cQPv6bMPwvB2QYBmBkd6LWJWrgFOI5FSVEROrv+cXGetf
|
||||||
|
N1ZfhJakTZi1VuxO5p4k5KcCgYAZS9OHR/jbfeZAkFOabzt/POVYYSIq1SnmxBVg
|
||||||
|
sFy57aTzxgXqd4XHWzi/GjxgEBCQiGp8zaB4KUEih6o3YlrVZC1wnvmvRxNuNbbT
|
||||||
|
HINqWzHgjyLs46gmxlMVzm/LUuiL5EMaWTuZeLk3h63RB6hk7jAtvd1zaLXnS+b8
|
||||||
|
5Kn+jQKBgQCDeMO6rvB2rbfqSbHvPPuTru1sPIsJBKm1YZpXTFI+VMjwtk7+meYb
|
||||||
|
UQnfZ1t5rjp9q4LEcRYuSa+PfifIkM6p+wMHVQhtltUCzXWWRYkLkmQrBWKu+qiP
|
||||||
|
edF6byMgXSzgOWYuRPXwmHpBQV0GiexQUAxVyUzaVWfil69LaFfXaw==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.assertTrue(provider.certificate_public_key)
|
||||||
|
|
||||||
|
def test_sca(self):
|
||||||
|
journal = self.AccountJournal.create({
|
||||||
|
'name': 'Bank',
|
||||||
|
'type': 'bank',
|
||||||
|
'code': 'BANK',
|
||||||
|
'currency_id': self.currency_eur.id,
|
||||||
|
'bank_statements_source': 'online',
|
||||||
|
'online_bank_statement_provider': 'transferwise',
|
||||||
|
})
|
||||||
|
|
||||||
|
provider = journal.online_bank_statement_provider_id
|
||||||
|
provider.origin = '1234567891'
|
||||||
|
provider.password = 'API_KEY'
|
||||||
|
provider.button_transferwise_generate_key()
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
'urllib.request.urlopen',
|
||||||
|
side_effect=[
|
||||||
|
self.response_balance,
|
||||||
|
self.response_ott,
|
||||||
|
self.response_transactions,
|
||||||
|
self.response_transactions,
|
||||||
|
self.response_transactions,
|
||||||
|
],
|
||||||
|
):
|
||||||
|
data = provider._obtain_statement_data(
|
||||||
|
self.now - relativedelta(hours=1),
|
||||||
|
self.now,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(data[0]), 0)
|
||||||
|
self.assertEqual(data[1]['balance_start'], 42.0)
|
||||||
|
self.assertEqual(data[1]['balance_end_real'], 42.0)
|
||||||
|
|
||||||
def test_transaction_parse_1(self):
|
def test_transaction_parse_1(self):
|
||||||
lines = self.transferwise_parse_transaction("""{
|
lines = self.transferwise_parse_transaction("""{
|
||||||
"type": "CREDIT",
|
"type": "CREDIT",
|
||||||
@@ -216,7 +395,7 @@ class TestAccountBankAccountStatementImportOnlineTransferwise(
|
|||||||
'amount': '-0.60',
|
'amount': '-0.60',
|
||||||
'name': 'Fee for TRANSFER-123456789',
|
'name': 'Fee for TRANSFER-123456789',
|
||||||
'note': 'Transaction fee for TRANSFER-123456789',
|
'note': 'Transaction fee for TRANSFER-123456789',
|
||||||
'partner_name': 'TransferWise',
|
'partner_name': 'Wise (former TransferWise)',
|
||||||
'unique_import_id': 'DEBIT-TRANSFER-123456789-946684800-FEE',
|
'unique_import_id': 'DEBIT-TRANSFER-123456789-946684800-FEE',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -342,7 +521,7 @@ class TestAccountBankAccountStatementImportOnlineTransferwise(
|
|||||||
'amount': '-1.23',
|
'amount': '-1.23',
|
||||||
'name': 'Fee for CARD-123456789',
|
'name': 'Fee for CARD-123456789',
|
||||||
'note': 'Transaction fee for CARD-123456789',
|
'note': 'Transaction fee for CARD-123456789',
|
||||||
'partner_name': 'TransferWise',
|
'partner_name': 'Wise (former TransferWise)',
|
||||||
'unique_import_id': 'DEBIT-CARD-123456789-946684800-FEE',
|
'unique_import_id': 'DEBIT-CARD-123456789-946684800-FEE',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -400,7 +579,7 @@ class TestAccountBankAccountStatementImportOnlineTransferwise(
|
|||||||
'date': datetime(2000, 1, 1),
|
'date': datetime(2000, 1, 1),
|
||||||
'name': 'Fee for TRANSFER-123456789',
|
'name': 'Fee for TRANSFER-123456789',
|
||||||
'note': 'Transaction fee for TRANSFER-123456789',
|
'note': 'Transaction fee for TRANSFER-123456789',
|
||||||
'partner_name': 'TransferWise',
|
'partner_name': 'Wise (former TransferWise)',
|
||||||
'amount': '-5.21',
|
'amount': '-5.21',
|
||||||
'unique_import_id': 'DEBIT-TRANSFER-123456789-946684800-FEE',
|
'unique_import_id': 'DEBIT-TRANSFER-123456789-946684800-FEE',
|
||||||
})
|
})
|
||||||
@@ -547,7 +726,7 @@ class TestAccountBankAccountStatementImportOnlineTransferwise(
|
|||||||
'name': 'Fee for BALANCE-123456789',
|
'name': 'Fee for BALANCE-123456789',
|
||||||
'note': 'Transaction fee for BALANCE-123456789',
|
'note': 'Transaction fee for BALANCE-123456789',
|
||||||
'amount': '-0.05',
|
'amount': '-0.05',
|
||||||
'partner_name': 'TransferWise',
|
'partner_name': 'Wise (former TransferWise)',
|
||||||
'unique_import_id': 'DEBIT-BALANCE-123456789-946684800-FEE',
|
'unique_import_id': 'DEBIT-BALANCE-123456789-946684800-FEE',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -587,7 +766,7 @@ class TestAccountBankAccountStatementImportOnlineTransferwise(
|
|||||||
'name': 'Fee for TRANSFER-123456789',
|
'name': 'Fee for TRANSFER-123456789',
|
||||||
'note': 'Transaction fee for TRANSFER-123456789',
|
'note': 'Transaction fee for TRANSFER-123456789',
|
||||||
'amount': '-0.68',
|
'amount': '-0.68',
|
||||||
'partner_name': 'TransferWise',
|
'partner_name': 'Wise (former TransferWise)',
|
||||||
'unique_import_id': 'CREDIT-TRANSFER-123456789-946684800-FEE',
|
'unique_import_id': 'CREDIT-TRANSFER-123456789-946684800-FEE',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -631,6 +810,6 @@ class TestAccountBankAccountStatementImportOnlineTransferwise(
|
|||||||
'name': 'Fee for TRANSFER-123456789',
|
'name': 'Fee for TRANSFER-123456789',
|
||||||
'note': 'Transaction fee for TRANSFER-123456789',
|
'note': 'Transaction fee for TRANSFER-123456789',
|
||||||
'amount': '4.33',
|
'amount': '4.33',
|
||||||
'partner_name': 'TransferWise',
|
'partner_name': 'Wise (former TransferWise)',
|
||||||
'unique_import_id': 'CREDIT-TRANSFER-123456789-946684800-FEE',
|
'unique_import_id': 'CREDIT-TRANSFER-123456789-946684800-FEE',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
Copyright 2019 Brainbean Apps (https://brainbeanapps.com)
|
Copyright 2019 Brainbean Apps (https://brainbeanapps.com)
|
||||||
|
Copyright 2021 CorporateHub (https://corporatehub.eu)
|
||||||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
-->
|
-->
|
||||||
<odoo>
|
<odoo>
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field
|
<field
|
||||||
name="origin"
|
name="transferwise_profile"
|
||||||
string="Profile"
|
string="Profile"
|
||||||
attrs="{'required': [('service', '=', 'transferwise')]}"
|
attrs="{'required': [('service', '=', 'transferwise')]}"
|
||||||
widget="dynamic_dropdown"
|
widget="dynamic_dropdown"
|
||||||
@@ -35,6 +36,25 @@
|
|||||||
context="{'api_key': password, 'api_base': api_base}"
|
context="{'api_key': password, 'api_base': api_base}"
|
||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
|
<group string="Strong Customer Authentication" colspan="4">
|
||||||
|
<field
|
||||||
|
name="certificate_private_key"
|
||||||
|
string="Private key"
|
||||||
|
password="True"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="certificate_public_key"
|
||||||
|
string="Public key"
|
||||||
|
/>
|
||||||
|
<div col="2" colspan="2">
|
||||||
|
<button
|
||||||
|
name="button_transferwise_generate_key"
|
||||||
|
string="Generate Key"
|
||||||
|
type="object"
|
||||||
|
class="oe_edit_only"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
|
|||||||
Reference in New Issue
Block a user