diff --git a/account_bank_statement_import_online_qonto/README.rst b/account_bank_statement_import_online_qonto/README.rst
new file mode 100644
index 0000000..3c6f16f
--- /dev/null
+++ b/account_bank_statement_import_online_qonto/README.rst
@@ -0,0 +1,103 @@
+=============================
+Online Bank Statements: Qonto
+=============================
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fbank--statement--import-lightgray.png?logo=github
+ :target: https://github.com/OCA/bank-statement-import/tree/12.0/account_bank_statement_import_online_qonto
+ :alt: OCA/bank-statement-import
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/bank-statement-import-12-0/bank-statement-import-12-0-account_bank_statement_import_online_qonto
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
+ :target: https://runbot.odoo-community.org/runbot/174/12.0
+ :alt: Try me on Runbot
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+This module provides online bank statements from Qonto Bank.
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Configuration
+=============
+
+To configure online bank statements provider:
+
+#. Go to *Invoicing > Configuration > Bank Accounts*
+#. Open bank account to configure and edit it
+#. Set *Bank Feeds* to *Online*
+#. Select *Qonto.eu* as online bank statements provider in
+ *Online Bank Statements (OCA)* section
+#. Save the bank account
+#. Click on provider and configure provider-specific settings.
+
+or, alternatively:
+
+#. Go to *Invoicing > Overview*
+#. Open settings of the corresponding journal account
+#. Switch to *Bank Account* tab
+#. Set *Bank Feeds* to *Online*
+#. Select *Qonto.eu* as online bank statements provider in
+ *Online Bank Statements (OCA)* section
+#. Save the bank account
+#. Click on provider and configure provider-specific settings.
+
+To obtain *Login* and *Key*:
+
+#. Open `Qonto.eu `_.
+#. Go to *Settings*
+#. Go to *Api Integration*, click *Generate Key*
+
+Usage
+=====
+
+To pull historical bank statements:
+
+#. Go to *Invoicing > Configuration > Bank Accounts*
+#. Select specific bank accounts
+#. Launch *Actions > Online Bank Statements Pull Wizard*
+#. Configure date interval and click *Pull*
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+~~~~~~~
+
+* Florent de Labarre
+
+Maintainers
+~~~~~~~~~~~
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+This module is part of the `OCA/bank-statement-import `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/account_bank_statement_import_online_qonto/__init__.py b/account_bank_statement_import_online_qonto/__init__.py
new file mode 100644
index 0000000..0650744
--- /dev/null
+++ b/account_bank_statement_import_online_qonto/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/account_bank_statement_import_online_qonto/__manifest__.py b/account_bank_statement_import_online_qonto/__manifest__.py
new file mode 100644
index 0000000..dfbc1d0
--- /dev/null
+++ b/account_bank_statement_import_online_qonto/__manifest__.py
@@ -0,0 +1,14 @@
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+{
+ "name": "Online Bank Statements: Qonto.eu",
+ "version": "12.0.1.0.0",
+ "category": "Account",
+ "website": "https://github.com/OCA/bank-statement-import",
+ "author": "Florent de Labarre, Odoo Community Association (OCA)",
+ "license": "AGPL-3",
+ "installable": True,
+ "depends": ["account_bank_statement_import_online"],
+ "data": [
+ "view/online_bank_statement_provider.xml"
+ ],
+}
diff --git a/account_bank_statement_import_online_qonto/models/__init__.py b/account_bank_statement_import_online_qonto/models/__init__.py
new file mode 100644
index 0000000..7a092b3
--- /dev/null
+++ b/account_bank_statement_import_online_qonto/models/__init__.py
@@ -0,0 +1,4 @@
+# Copyright 2020 Florent de Labarre
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from . import online_bank_statement_provider_qonto
diff --git a/account_bank_statement_import_online_qonto/models/online_bank_statement_provider_qonto.py b/account_bank_statement_import_online_qonto/models/online_bank_statement_provider_qonto.py
new file mode 100644
index 0000000..e421fc7
--- /dev/null
+++ b/account_bank_statement_import_online_qonto/models/online_bank_statement_provider_qonto.py
@@ -0,0 +1,157 @@
+# Copyright 2020 Florent de Labarre
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+import requests
+import json
+from datetime import datetime
+import pytz
+
+from odoo import api, models, _
+from odoo.exceptions import UserError
+from odoo.addons.base.models.res_bank import sanitize_account_number
+
+QONTO_ENDPOINT = 'https://thirdparty.qonto.eu/v2'
+
+
+class OnlineBankStatementProviderQonto(models.Model):
+ _inherit = 'online.bank.statement.provider'
+
+ @api.model
+ def _get_available_services(self):
+ return super()._get_available_services() + [
+ ('qonto', 'Qonto.eu'),
+ ]
+
+ def _obtain_statement_data(self, date_since, date_until):
+ self.ensure_one()
+ if self.service != 'qonto':
+ return super()._obtain_statement_data(
+ date_since,
+ date_until,
+ )
+ return self._qonto_obtain_statement_data(date_since, date_until)
+
+ def _get_statement_date(self, date_since, date_until):
+ self.ensure_one()
+ if self.service != 'qonto':
+ return super()._get_statement_date(
+ date_since,
+ date_until,
+ )
+ return date_since.astimezone(pytz.timezone('Europe/Paris')).date()
+
+ #########
+ # qonto #
+ #########
+
+ def _qonto_header(self):
+ self.ensure_one()
+ if self.username and self.password:
+ return {'Authorization': '%s:%s' % (self.username, self.password)}
+ raise UserError(_('Please fill login and key'))
+
+ def _qonto_get_slug(self):
+ self.ensure_one()
+ url = QONTO_ENDPOINT + '/organizations/%7Bid%7D'
+ response = requests.get(url, verify=False, headers=self._qonto_header())
+ if response.status_code == 200:
+ data = json.loads(response.text)
+ res = {}
+ for account in data.get('organization', {}).get('bank_accounts', []):
+ iban = sanitize_account_number(account.get('iban', ''))
+ res[iban] = account.get('slug')
+ return res
+ raise UserError(_('%s \n\n %s') % (response.status_code, response.text))
+
+ def _qonto_obtain_transactions(self, slug, date_since, date_until):
+ self.ensure_one()
+ url = QONTO_ENDPOINT + '/transactions'
+ params = {'slug': slug, 'iban': self.account_number}
+
+ if date_since:
+ params['settled_at_from'] = date_since.replace(
+ microsecond=0).isoformat() + 'Z'
+ if date_until:
+ params['settled_at_to'] = date_until.replace(
+ microsecond=0).isoformat() + 'Z'
+ transactions = []
+ current_page = 1
+ total_pages = 1
+ while current_page <= total_pages:
+ params['current_page'] = current_page
+ data = self._qonto_get_transactions(url, params)
+ transactions.extend(data.get('transactions', []))
+ total_pages = data['meta']['total_pages']
+ current_page += 1
+ return transactions
+
+ def _qonto_get_transactions(self, url, params):
+ response = requests.get(url, verify=False, params=params,
+ headers=self._qonto_header())
+ if response.status_code == 200:
+ return json.loads(response.text)
+ raise UserError(_('%s \n\n %s') % (response.status_code, response.text))
+
+ def _qonto_prepare_statement_line(
+ self, transaction, sequence, journal_currency, currencies_code2id):
+ date = datetime.strptime(transaction['settled_at'], '%Y-%m-%dT%H:%M:%S.%fZ')
+ side = 1 if transaction['side'] == 'credit' else -1
+ name = transaction['label'] or '/'
+ if transaction['reference']:
+ name = '%s %s' % (name, transaction['reference'])
+ vals_line = {
+ 'sequence': sequence,
+ 'date': date,
+ 'name': name,
+ 'ref': transaction['reference'],
+ 'unique_import_id': transaction['transaction_id'],
+ 'amount': transaction['amount'] * side,
+ }
+
+ if not transaction['local_currency']:
+ raise UserError(_(
+ "Transaction ID %s has not local_currency. "
+ "This should never happen.") % transaction['transaction_id'])
+ if transaction['local_currency'] not in currencies_code2id:
+ raise UserError(_(
+ "Currency %s used in transaction ID %s doesn't exist "
+ "in Odoo.") % (
+ transaction['local_currency'],
+ transaction['transaction_id']))
+
+ line_currency_id = currencies_code2id[transaction['local_currency']]
+
+ if journal_currency.id != line_currency_id:
+ vals_line.update({
+ 'currency_id': line_currency_id,
+ 'amount_currency': transaction['local_amount'] * side,
+ })
+ return vals_line
+
+ def _qonto_obtain_statement_data(self, date_since, date_until):
+ self.ensure_one()
+ journal = self.journal_id
+
+ slugs = self._qonto_get_slug()
+ slug = slugs.get(self.account_number)
+ if not slug:
+ raise UserError(
+ _('Qonto : wrong configuration, unknow account %s')
+ % journal.bank_account_id.acc_number)
+
+ transactions = self._qonto_obtain_transactions(slug, date_since, date_until)
+
+ journal_currency = journal.currency_id or journal.company_id.currency_id
+
+ all_currencies = self.env['res.currency'].search_read([], ['name'])
+ currencies_code2id = dict([(x['name'], x['id']) for x in all_currencies])
+ new_transactions = []
+ sequence = 0
+ for transaction in transactions:
+ sequence += 1
+ vals_line = self._qonto_prepare_statement_line(
+ transaction, sequence, journal_currency, currencies_code2id)
+ new_transactions.append(vals_line)
+
+ if new_transactions:
+ return new_transactions, {}
+ return
diff --git a/account_bank_statement_import_online_qonto/static/description/icon.png b/account_bank_statement_import_online_qonto/static/description/icon.png
new file mode 100644
index 0000000..3a0328b
Binary files /dev/null and b/account_bank_statement_import_online_qonto/static/description/icon.png differ
diff --git a/account_bank_statement_import_online_qonto/tests/__init__.py b/account_bank_statement_import_online_qonto/tests/__init__.py
new file mode 100644
index 0000000..148eb15
--- /dev/null
+++ b/account_bank_statement_import_online_qonto/tests/__init__.py
@@ -0,0 +1,3 @@
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from . import test_account_bank_statement_import_online_qonto
diff --git a/account_bank_statement_import_online_qonto/tests/test_account_bank_statement_import_online_qonto.py b/account_bank_statement_import_online_qonto/tests/test_account_bank_statement_import_online_qonto.py
new file mode 100644
index 0000000..98b3856
--- /dev/null
+++ b/account_bank_statement_import_online_qonto/tests/test_account_bank_statement_import_online_qonto.py
@@ -0,0 +1,120 @@
+# Copyright 2020 Florent de Labarre
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from datetime import datetime
+from unittest import mock
+
+from odoo import fields
+from odoo.tests import common
+
+_module_ns = 'odoo.addons.account_bank_statement_import_online_qonto'
+_provider_class = (
+ _module_ns
+ + '.models.online_bank_statement_provider_qonto'
+ + '.OnlineBankStatementProviderQonto'
+)
+
+
+class TestAccountBankAccountStatementImportOnlineQonto(
+ common.TransactionCase
+):
+
+ def setUp(self):
+ super().setUp()
+
+ self.now = fields.Datetime.now()
+ self.currency_eur = self.env.ref('base.EUR')
+ self.currency_usd = self.env.ref('base.USD')
+ self.AccountJournal = self.env['account.journal']
+ self.ResPartnerBank = self.env['res.partner.bank']
+ self.OnlineBankStatementProvider = self.env[
+ 'online.bank.statement.provider'
+ ]
+ self.AccountBankStatement = self.env['account.bank.statement']
+ self.AccountBankStatementLine = self.env['account.bank.statement.line']
+
+ self.bank_account = self.ResPartnerBank.create(
+ {'acc_number': 'FR0214508000302245362775K46',
+ 'partner_id': self.env.user.company_id.partner_id.id})
+ 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': 'qonto',
+ 'bank_account_id': self.bank_account.id,
+ })
+ self.provider = self.journal.online_bank_statement_provider_id
+
+ self.mock_slug = lambda: mock.patch(
+ _provider_class + '._qonto_get_slug',
+ return_value={'FR0214508000302245362775K46': 'qonto-1234-bank-account-1'},
+ )
+ self.mock_transaction = lambda: mock.patch(
+ _provider_class + '._qonto_get_transactions',
+ return_value={
+ "transactions": [
+ {"transaction_id": "qonto-1234-1-transaction-3",
+ "amount": 1200.0,
+ "amount_cents": 120000,
+ "attachment_ids": [],
+ "local_amount": 1200.0,
+ "local_amount_cents": 120000,
+ "side": "credit",
+ "operation_type": "income",
+ "currency": "EUR",
+ "local_currency": "EUR",
+ "label": "INVOICE A",
+ "settled_at": "2020-04-16T07:01:55.503Z",
+ "emitted_at": "2020-04-16T05:01:55.000Z",
+ "updated_at": "2020-04-16T07:04:02.792Z",
+ "status": "completed",
+ "note": None,
+ "reference": "Ref 1233",
+ "vat_amount": None,
+ "vat_amount_cents": None,
+ "vat_rate": None,
+ "initiator_id": None,
+ "label_ids": [],
+ "attachment_lost": False,
+ "attachment_required": True},
+ {"transaction_id": "qonto-1234-1-transaction-2",
+ "amount": 1128.36, "amount_cents": 112836,
+ "attachment_ids": [],
+ "local_amount": 1128.36,
+ "local_amount_cents": 112836,
+ "side": "debit",
+ "operation_type": "transfer",
+ "currency": "EUR",
+ "local_currency": "EUR",
+ "label": "BILL A",
+ "settled_at": "2020-04-16T07:00:30.979Z",
+ "emitted_at": "2020-04-15T18:22:30.296Z",
+ "updated_at": "2020-04-16T07:03:01.125Z",
+ "status": "completed",
+ "note": None,
+ "reference": "Invoice",
+ "vat_amount": None,
+ "vat_amount_cents": None,
+ "vat_rate": None,
+ "initiator_id": "9b783957-85a6-404a-8320-a298781cb5fa",
+ "label_ids": [],
+ "attachment_lost": False,
+ "attachment_required": True}],
+ "meta": {"current_page": 1,
+ "next_page": None,
+ "prev_page": None,
+ "total_pages": 1,
+ "total_count": 2,
+ "per_page": 100}},
+ )
+
+ def test_qonto(self):
+ with self.mock_transaction(), self.mock_slug():
+ lines, statement_values = self.provider._obtain_statement_data(
+ datetime(2020, 4, 15),
+ datetime(2020, 4, 17),
+ )
+
+ self.assertEqual(len(lines), 2)
diff --git a/account_bank_statement_import_online_qonto/view/online_bank_statement_provider.xml b/account_bank_statement_import_online_qonto/view/online_bank_statement_provider.xml
new file mode 100644
index 0000000..7bc06c9
--- /dev/null
+++ b/account_bank_statement_import_online_qonto/view/online_bank_statement_provider.xml
@@ -0,0 +1,16 @@
+
+
+
+ online.bank.statement.provider.form
+ online.bank.statement.provider
+
+
+
+
+
+
+
+
+
+
+