mirror of
https://github.com/OCA/bank-statement-import.git
synced 2025-01-20 12:37:43 +02:00
[FIX] account_bank_statement_import_online_paypal: workaround for PayPal issue
This commit is contained in:
committed by
Omar (Comunitea)
parent
69499ba32a
commit
53c659796d
@@ -86,6 +86,10 @@ Known issues / Roadmap
|
||||
* `PayPal Transaction Info <https://developer.paypal.com/docs/api/sync/v1/#definition-transaction_info>`_
|
||||
defines extra fields like ``tip_amount``, ``shipping_amount``, etc. that
|
||||
could be useful to be decomposed from a single transaction.
|
||||
* There's a known issue with PayPal API that on every Monday for couple of
|
||||
hours after UTC midnight it returns ``INVALID_REQUEST`` incorrectly: their
|
||||
servers have not inflated the data yet. PayPal tech support confirmed this
|
||||
behaviour in case #06650320 (private).
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
{
|
||||
'name': 'Online Bank Statements: PayPal.com',
|
||||
'version': '12.0.1.0.0',
|
||||
'version': '12.0.1.0.1',
|
||||
'author':
|
||||
'Brainbean Apps, '
|
||||
'Dataplug, '
|
||||
|
||||
@@ -234,20 +234,20 @@ msgid "Electronic funds transfer (EFT)"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import_online_paypal
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:362
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:363
|
||||
#, python-format
|
||||
msgid "Failed to acquire token using Client ID and Secret!"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import_online_paypal
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:240
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:266
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:241
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:267
|
||||
#, python-format
|
||||
msgid "Failed to resolve transaction %s (%s)"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import_online_paypal
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:336
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:337
|
||||
#, python-format
|
||||
msgid "Fee for %s"
|
||||
msgstr ""
|
||||
@@ -500,13 +500,13 @@ msgid "International credit card withdrawal"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import_online_paypal
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:360
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:361
|
||||
#, python-format
|
||||
msgid "Invalid token type!"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import_online_paypal
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:302
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:303
|
||||
#, python-format
|
||||
msgid "Invoice %s"
|
||||
msgstr ""
|
||||
@@ -554,7 +554,7 @@ msgid "Mobile payment, made through a mobile phone"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import_online_paypal
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:500
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:522
|
||||
#, python-format
|
||||
msgid "No authentication specified!"
|
||||
msgstr ""
|
||||
@@ -589,7 +589,7 @@ msgid "Partner fee"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import_online_paypal
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:356
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:357
|
||||
#, python-format
|
||||
msgid "PayPal App features are configured incorrectly!"
|
||||
msgstr ""
|
||||
@@ -607,7 +607,7 @@ msgid "PayPal Here payment"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import_online_paypal
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:199
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:200
|
||||
#, python-format
|
||||
msgid "PayPal allows retrieving transactions only up to 3 years in the past. Please import older transactions manually. See https://www.paypal.com/us/smarthelp/article/why-can't-i-access-transaction-history-greater-than-3-years-ts2241"
|
||||
msgstr ""
|
||||
@@ -828,7 +828,7 @@ msgid "Third-party recoupment"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import_online_paypal
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:341
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:342
|
||||
#, python-format
|
||||
msgid "Transaction fee for %s"
|
||||
msgstr ""
|
||||
@@ -852,11 +852,18 @@ msgid "Transfer to external GL entity"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import_online_paypal
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:516
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:538
|
||||
#, python-format
|
||||
msgid "Unknown authentication specified!"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import_online_paypal
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:485
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:492
|
||||
#, python-format
|
||||
msgid "Unknown error"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import_online_paypal
|
||||
#: code:addons/account_bank_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:59
|
||||
#, python-format
|
||||
|
||||
@@ -166,6 +166,7 @@ EVENT_DESCRIPTIONS = {
|
||||
'T9800': _('Display only transaction'),
|
||||
'T9900': _('Other'),
|
||||
}
|
||||
NO_DATA_FOR_DATE_AVAIL_MSG = 'Data for the given start date is not available.'
|
||||
|
||||
|
||||
class OnlineBankStatementProviderPayPal(models.Model):
|
||||
@@ -416,7 +417,18 @@ class OnlineBankStatementProviderPayPal(models.Model):
|
||||
interval_end.isoformat() + 'Z',
|
||||
page,
|
||||
))
|
||||
data = self._paypal_retrieve(url, token)
|
||||
|
||||
# NOTE: Workaround for INVALID_REQUEST (see ROADMAP.rst)
|
||||
invalid_data_workaround = self.env.context.get(
|
||||
'test_account_bank_statement_import_online_paypal_monday',
|
||||
interval_start.weekday() == 0 and (
|
||||
datetime.utcnow() - interval_start
|
||||
).total_seconds() < 28800
|
||||
)
|
||||
|
||||
data = self.with_context(
|
||||
invalid_data_workaround=invalid_data_workaround,
|
||||
)._paypal_retrieve(url, token)
|
||||
interval_transactions = map(
|
||||
lambda transaction: self._paypal_preparse_transaction(
|
||||
transaction
|
||||
@@ -465,15 +477,22 @@ class OnlineBankStatementProviderPayPal(models.Model):
|
||||
return Decimal(transaction_amount['value'])
|
||||
|
||||
@api.model
|
||||
def _paypal_validate(self, content):
|
||||
content = json.loads(content)
|
||||
if 'error' in content and content['error']:
|
||||
raise UserError(
|
||||
content['error_description']
|
||||
if 'error_description' in content
|
||||
else 'Unknown error'
|
||||
)
|
||||
return content
|
||||
def _paypal_decode_error(self, content):
|
||||
generic_error = content.get('name')
|
||||
if generic_error:
|
||||
return UserError('%s: %s' % (
|
||||
generic_error,
|
||||
content.get('message') or _('Unknown error'),
|
||||
))
|
||||
|
||||
identity_error = content.get('error')
|
||||
if identity_error:
|
||||
UserError('%s: %s' % (
|
||||
generic_error,
|
||||
content.get('error_description') or _('Unknown error'),
|
||||
))
|
||||
|
||||
return None
|
||||
|
||||
@api.model
|
||||
def _paypal_retrieve(self, url, auth, data=None):
|
||||
@@ -481,18 +500,21 @@ class OnlineBankStatementProviderPayPal(models.Model):
|
||||
with self._paypal_urlopen(url, auth, data) as response:
|
||||
content = response.read().decode('utf-8')
|
||||
except HTTPError as e:
|
||||
content = self._paypal_validate(
|
||||
e.read().decode('utf-8')
|
||||
)
|
||||
if 'name' in content and content['name']:
|
||||
raise UserError('%s: %s' % (
|
||||
content['name'],
|
||||
content['error_description']
|
||||
if 'error_description' in content
|
||||
else 'Unknown error',
|
||||
))
|
||||
raise e
|
||||
return self._paypal_validate(content)
|
||||
content = json.loads(e.read().decode('utf-8'))
|
||||
|
||||
# NOTE: Workaround for INVALID_REQUEST (see ROADMAP.rst)
|
||||
if self.env.context.get('invalid_data_workaround') \
|
||||
and content.get('name') == 'INVALID_REQUEST' \
|
||||
and content.get('message') == NO_DATA_FOR_DATE_AVAIL_MSG:
|
||||
return {
|
||||
'transaction_details': [],
|
||||
'page': 1,
|
||||
'total_items': 0,
|
||||
'total_pages': 0,
|
||||
}
|
||||
|
||||
raise self._paypal_decode_error(content) or e
|
||||
return json.loads(content)
|
||||
|
||||
@api.model
|
||||
def _paypal_urlopen(self, url, auth, data=None):
|
||||
|
||||
@@ -5,3 +5,7 @@
|
||||
* `PayPal Transaction Info <https://developer.paypal.com/docs/api/sync/v1/#definition-transaction_info>`_
|
||||
defines extra fields like ``tip_amount``, ``shipping_amount``, etc. that
|
||||
could be useful to be decomposed from a single transaction.
|
||||
* There's a known issue with PayPal API that on every Monday for couple of
|
||||
hours after UTC midnight it returns ``INVALID_REQUEST`` incorrectly: their
|
||||
servers have not inflated the data yet. PayPal tech support confirmed this
|
||||
behaviour in case #06650320 (private).
|
||||
|
||||
@@ -438,6 +438,10 @@ for details.</li>
|
||||
<li><a class="reference external" href="https://developer.paypal.com/docs/api/sync/v1/#definition-transaction_info">PayPal Transaction Info</a>
|
||||
defines extra fields like <tt class="docutils literal">tip_amount</tt>, <tt class="docutils literal">shipping_amount</tt>, etc. that
|
||||
could be useful to be decomposed from a single transaction.</li>
|
||||
<li>There’s a known issue with PayPal API that on every Monday for couple of
|
||||
hours after UTC midnight it returns <tt class="docutils literal">INVALID_REQUEST</tt> incorrectly: their
|
||||
servers have not inflated the data yet. PayPal tech support confirmed this
|
||||
behaviour in case #06650320 (private).</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
|
||||
@@ -7,9 +7,11 @@ from dateutil.relativedelta import relativedelta
|
||||
from decimal import Decimal
|
||||
import json
|
||||
from unittest import mock
|
||||
from urllib.error import HTTPError
|
||||
|
||||
from odoo.tests import common
|
||||
from odoo import fields
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import common
|
||||
|
||||
_module_ns = 'odoo.addons.account_bank_statement_import_online_paypal'
|
||||
_provider_class = (
|
||||
@@ -19,6 +21,31 @@ _provider_class = (
|
||||
)
|
||||
|
||||
|
||||
class FakeHTTPError(HTTPError):
|
||||
def __init__(self, content):
|
||||
self.content = content
|
||||
|
||||
def read(self):
|
||||
return self.content.encode('utf-8')
|
||||
|
||||
|
||||
class UrlopenRetValMock:
|
||||
def __init__(self, content, throw=False):
|
||||
self.content = content
|
||||
self.throw = throw
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, tb):
|
||||
pass
|
||||
|
||||
def read(self):
|
||||
if self.throw:
|
||||
raise FakeHTTPError(self.content)
|
||||
return self.content.encode('utf-8')
|
||||
|
||||
|
||||
class TestAccountBankAccountStatementImportOnlinePayPal(
|
||||
common.TransactionCase
|
||||
):
|
||||
@@ -156,6 +183,87 @@ class TestAccountBankAccountStatementImportOnlinePayPal(
|
||||
with self.assertRaises(Exception):
|
||||
provider._paypal_get_token()
|
||||
|
||||
def test_no_data_on_monday(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': 'paypal',
|
||||
})
|
||||
|
||||
provider = journal.online_bank_statement_provider_id
|
||||
mocked_response = UrlopenRetValMock("""{
|
||||
"debug_id": "eec890ebd5798",
|
||||
"details": "xxxxxx",
|
||||
"links": "xxxxxx",
|
||||
"message": "Data for the given start date is not available.",
|
||||
"name": "INVALID_REQUEST"
|
||||
}""", throw=True)
|
||||
with mock.patch(
|
||||
_provider_class + '._paypal_urlopen',
|
||||
return_value=mocked_response,
|
||||
), self.mock_token():
|
||||
data = provider.with_context(
|
||||
test_account_bank_statement_import_online_paypal_monday=True,
|
||||
)._obtain_statement_data(
|
||||
self.now - relativedelta(hours=1),
|
||||
self.now,
|
||||
)
|
||||
|
||||
self.assertIsNone(data)
|
||||
|
||||
def test_error_handling_1(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': 'paypal',
|
||||
})
|
||||
|
||||
provider = journal.online_bank_statement_provider_id
|
||||
mocked_response = UrlopenRetValMock("""{
|
||||
"message": "MSG",
|
||||
"name": "ERROR"
|
||||
}""", throw=True)
|
||||
with mock.patch(
|
||||
_provider_class + '._paypal_urlopen',
|
||||
return_value=mocked_response,
|
||||
), self.mock_token():
|
||||
with self.assertRaises(UserError):
|
||||
provider._obtain_statement_data(
|
||||
self.now - relativedelta(years=5),
|
||||
self.now,
|
||||
)
|
||||
|
||||
def test_error_handling_2(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': 'paypal',
|
||||
})
|
||||
|
||||
provider = journal.online_bank_statement_provider_id
|
||||
mocked_response = UrlopenRetValMock("""{
|
||||
"error_description": "DESC",
|
||||
"error": "ERROR"
|
||||
}""", throw=True)
|
||||
with mock.patch(
|
||||
_provider_class + '._paypal_urlopen',
|
||||
return_value=mocked_response,
|
||||
), self.mock_token():
|
||||
with self.assertRaises(UserError):
|
||||
provider._obtain_statement_data(
|
||||
self.now - relativedelta(years=5),
|
||||
self.now,
|
||||
)
|
||||
|
||||
def test_empty_pull(self):
|
||||
journal = self.AccountJournal.create({
|
||||
'name': 'Bank',
|
||||
|
||||
Reference in New Issue
Block a user