Merge PR #452 into 14.0

Signed-off-by pedrobaeza
This commit is contained in:
OCA-git-bot
2022-05-29 15:39:36 +00:00
18 changed files with 3022 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
==================================
Online Bank Statements: PayPal.com
==================================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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/13.0/account_bank_statement_import_online_paypal
: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-13-0/bank-statement-import-13-0-account_bank_statement_import_online_paypal
: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/13.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
This module provides online bank statements from
`PayPal.com <https://paypal.com/>`__.
**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 *PayPal.com* 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 *PayPal.com* 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 *Client ID* and *Secret*:
#. Open `PayPal Developer <https://developer.paypal.com/developer/applications/>`_
#. Go to *My Apps & Credentials* and switch to *Live*
#. Under *REST API apps*, click *Create App* to create new application (e.g. *Odoo*)
#. Copy *Client ID* and *Secret* to use during provider configuration
#. Under *Live App Settings*, uncheck all features except *Transaction Search*
#. Click Save
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*
Known issues / Roadmap
======================
* Only transactions for the previous three years are retrieved, historical data
can be imported manually, see ``account_bank_statement_import_paypal``. See
`PayPal Help Center article <https://www.paypal.com/us/smarthelp/article/why-can't-i-access-transaction-history-greater-than-3-years-ts2241>`_
for details.
* `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
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/bank-statement-import/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 <https://github.com/OCA/bank-statement-import/issues/new?body=module:%20account_bank_statement_import_online_paypal%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* CorporateHub
Contributors
~~~~~~~~~~~~
* `CorporateHub <https://corporatehub.eu/>`__
* Alexey Pelykh <alexey.pelykh@corphub.eu>
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.
.. |maintainer-alexey-pelykh| image:: https://github.com/alexey-pelykh.png?size=40px
:target: https://github.com/alexey-pelykh
:alt: alexey-pelykh
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-alexey-pelykh|
This module is part of the `OCA/bank-statement-import <https://github.com/OCA/bank-statement-import/tree/13.0/account_bank_statement_import_online_paypal>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import models

View File

@@ -0,0 +1,17 @@
# Copyright 2019 Brainbean Apps (https://brainbeanapps.com)
# Copyright 2020-2021 CorporateHub (https://corporatehub.eu)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Online Bank Statements: PayPal.com",
"version": "14.0.1.0.0",
"author": "CorporateHub, Odoo Community Association (OCA)",
"maintainers": ["alexey-pelykh"],
"website": "https://github.com/OCA/bank-statement-import",
"license": "AGPL-3",
"category": "Accounting",
"summary": "Online bank statements for PayPal.com",
"depends": ["account_statement_import_online"],
"data": ["views/online_bank_statement_provider.xml"],
"installable": True,
}

View File

@@ -0,0 +1,937 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_statement_import_online_paypal
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "ACH funding for funds recovery from account balance"
msgstr ""
#. module: account_statement_import_online_paypal
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_paypal.online_bank_statement_provider_form
msgid "API base"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "ATM withdrawal"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Account hold for ACH deposit"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Account hold for open authorization"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Account receivable for shipping"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Auto-sweep from account"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "AutoSweep"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "BML credit, transfer from BML"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "BML withdrawal, transfer to BML"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Balance manager account bonus"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Bill pay transaction"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Blocked payments"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Bonus for first ACH use"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Buyer credit payment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Buyer credit payment withdrawal, transfer to BML"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Cancellation of hold for dispute resolution"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Charge-off adjustment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Chargeback"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Chargeback cancellation"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Chargeback processing fee"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Chargeback re-presentment rejection"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Chargeback reversal"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Check withdrawal"
msgstr ""
#. module: account_statement_import_online_paypal
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_paypal.online_bank_statement_provider_form
msgid "Client ID"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Coupon redemption"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Credit card cash back bonus"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Credit card deposit for negative PayPal account balance"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Credit card security charge refund"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Currency conversion required to cover negative balance"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Debit card cash back bonus"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Deferred disbursement, funds collected for disbursement"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Delayed disbursement, funds disbursed"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Direct payment API"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Display only transaction"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Donation payment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Electronic funds transfer (EFT)"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Failed to acquire token using Client ID and Secret!"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Failed to resolve transaction %s (%s)"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Fee for %s"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Fee refund"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Fee reversal"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Foreign bank withdrawal fee"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Funds available"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Funds not yet available"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Funds payable: PayPal-provided funds that must be paid back"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Funds receivable: PayPal-provided funds that are being paid back"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General PayPal debit card transaction"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General PayPal-to-PayPal payment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General account adjustment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General adjustment without business-related event"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General authorization"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General bonus"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General buyer credit payment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General credit card deposit"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General credit card withdrawal"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General currency conversion"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General dividend"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General funding of PayPal account"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General hold"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General hold release"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General incentive or certificate redemption"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General intra-account transfer"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General non-payment fee"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General reversal"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General temporary hold"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General withdrawal from PayPal account"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "General withdrawal to non-bank institution"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Generic instrument-funded payment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Generic instrument/Open Wallet reversals (buyer side)"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Generic instrument/Open Wallet reversals (seller side)"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Generic instrument/Open Wallet transaction"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Gift certificate expiration fee"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Gift certificate payment, purchase of gift certificate"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Gift certificate purchase"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Gift certificate redemption"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Hidden virtual PayPal debit card transaction"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Hold for dispute investigation"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Incentive adjustment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Instant payment review (IPR) reversal"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "International credit card withdrawal"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Invalid token type!"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Invoice %s"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "MAM reversal"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Mass payment batch fee"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "MassPay payment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "MassPay refund transaction"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "MassPay reversal transaction"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Merchant referral account bonus"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Mobile payment, made through a mobile phone"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "No authentication specified!"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Non-reference credit payment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Offers used as funding source"
msgstr ""
#. module: account_statement_import_online_paypal
#: model:ir.model,name:account_statement_import_online_paypal.model_online_bank_statement_provider
msgid "Online Bank Statement Provider"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Other"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Partner fee"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "PayPal App features are configured incorrectly!"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "PayPal Checkout APIs"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "PayPal Here payment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, 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 ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "PayPal balance manager funding of PayPal account"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "PayPal buyer credit payment funding"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "PayPal buyer warranty bonus"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "PayPal debit authorization"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "PayPal debit card cash advance"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "PayPal debit card withdrawal to ATM"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid ""
"PayPal protection bonus, payout for PayPal buyer protection, payout for full"
" protection with PayPal buyer credit."
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Payment fee"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Payment hold"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Payment hold release"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Payment refund, initiated by merchant"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Payment reversal, initiated by PayPal"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Payment review hold"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Payment review release"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Points incentive redemption"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Postage payment to carrier"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Pre-approved payment (BillUser API)"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Reauthorization"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Rebate or cash back reversal"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Rebate payments"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Reimbursement of chargeback"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Reserve hold"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Reserve release"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Reversal of ACH deposit"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Reversal of ACH withdrawal transaction"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Reversal of debit card transaction"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Reversal of general account hold"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Reversal of points usage"
msgstr ""
#. module: account_statement_import_online_paypal
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_paypal.online_bank_statement_provider_form
msgid "Secret"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Settlement consolidation"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Store-to-store transfers"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Subscription payment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Temporary hold on available balance"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Third-party auction payment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Third-party payout"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Third-party recoupment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Transaction fee for %s"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Transfer of funds from payable"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Transfer to and from a credit-card-funded restricted balance"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Transfer to external GL entity"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Unknown authentication specified!"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Unknown error"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "User-initiated currency conversion"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Virtual PayPal debit card transaction"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Virtual terminal payment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Void of authorization"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Warranty fee for warranty purchase"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Website payments standard payment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "Website payments. Pro account monthly fee"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "WorldLink check withdrawal fee"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "WorldLink withdrawal"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "eBay auction payment"
msgstr ""
#. module: account_statement_import_online_paypal
#: code:addons/account_statement_import_online_paypal/models/online_bank_statement_provider_paypal.py:0
#, python-format
msgid "eBay loyalty incentive"
msgstr ""

View File

@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import online_bank_statement_provider_paypal

View File

@@ -0,0 +1,520 @@
# Copyright 2019-2020 Brainbean Apps (https://brainbeanapps.com)
# Copyright 2021 CorporateHub (https://corporatehub.eu)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import itertools
import json
import urllib.request
from base64 import b64encode
from datetime import datetime
from decimal import Decimal
from urllib.error import HTTPError
from urllib.parse import urlencode
import dateutil.parser
import pytz
from dateutil.relativedelta import relativedelta
from odoo import _, api, models
from odoo.exceptions import UserError
PAYPAL_API_BASE = "https://api.paypal.com"
TRANSACTIONS_SCOPE = "https://uri.paypal.com/services/reporting/search/read"
EVENT_DESCRIPTIONS = {
"T0000": _("General PayPal-to-PayPal payment"),
"T0001": _("MassPay payment"),
"T0002": _("Subscription payment"),
"T0003": _("Pre-approved payment (BillUser API)"),
"T0004": _("eBay auction payment"),
"T0005": _("Direct payment API"),
"T0006": _("PayPal Checkout APIs"),
"T0007": _("Website payments standard payment"),
"T0008": _("Postage payment to carrier"),
"T0009": _("Gift certificate payment, purchase of gift certificate"),
"T0010": _("Third-party auction payment"),
"T0011": _("Mobile payment, made through a mobile phone"),
"T0012": _("Virtual terminal payment"),
"T0013": _("Donation payment"),
"T0014": _("Rebate payments"),
"T0015": _("Third-party payout"),
"T0016": _("Third-party recoupment"),
"T0017": _("Store-to-store transfers"),
"T0018": _("PayPal Here payment"),
"T0019": _("Generic instrument-funded payment"),
"T0100": _("General non-payment fee"),
"T0101": _("Website payments. Pro account monthly fee"),
"T0102": _("Foreign bank withdrawal fee"),
"T0103": _("WorldLink check withdrawal fee"),
"T0104": _("Mass payment batch fee"),
"T0105": _("Check withdrawal"),
"T0106": _("Chargeback processing fee"),
"T0107": _("Payment fee"),
"T0108": _("ATM withdrawal"),
"T0109": _("Auto-sweep from account"),
"T0110": _("International credit card withdrawal"),
"T0111": _("Warranty fee for warranty purchase"),
"T0112": _("Gift certificate expiration fee"),
"T0113": _("Partner fee"),
"T0200": _("General currency conversion"),
"T0201": _("User-initiated currency conversion"),
"T0202": _("Currency conversion required to cover negative balance"),
"T0300": _("General funding of PayPal account"),
"T0301": _("PayPal balance manager funding of PayPal account"),
"T0302": _("ACH funding for funds recovery from account balance"),
"T0303": _("Electronic funds transfer (EFT)"),
"T0400": _("General withdrawal from PayPal account"),
"T0401": _("AutoSweep"),
"T0500": _("General PayPal debit card transaction"),
"T0501": _("Virtual PayPal debit card transaction"),
"T0502": _("PayPal debit card withdrawal to ATM"),
"T0503": _("Hidden virtual PayPal debit card transaction"),
"T0504": _("PayPal debit card cash advance"),
"T0505": _("PayPal debit authorization"),
"T0600": _("General credit card withdrawal"),
"T0700": _("General credit card deposit"),
"T0701": _("Credit card deposit for negative PayPal account balance"),
"T0800": _("General bonus"),
"T0801": _("Debit card cash back bonus"),
"T0802": _("Merchant referral account bonus"),
"T0803": _("Balance manager account bonus"),
"T0804": _("PayPal buyer warranty bonus"),
"T0805": _(
"PayPal protection bonus, payout for PayPal buyer protection, payout "
"for full protection with PayPal buyer credit."
),
"T0806": _("Bonus for first ACH use"),
"T0807": _("Credit card security charge refund"),
"T0808": _("Credit card cash back bonus"),
"T0900": _("General incentive or certificate redemption"),
"T0901": _("Gift certificate redemption"),
"T0902": _("Points incentive redemption"),
"T0903": _("Coupon redemption"),
"T0904": _("eBay loyalty incentive"),
"T0905": _("Offers used as funding source"),
"T1000": _("Bill pay transaction"),
"T1100": _("General reversal"),
"T1101": _("Reversal of ACH withdrawal transaction"),
"T1102": _("Reversal of debit card transaction"),
"T1103": _("Reversal of points usage"),
"T1104": _("Reversal of ACH deposit"),
"T1105": _("Reversal of general account hold"),
"T1106": _("Payment reversal, initiated by PayPal"),
"T1107": _("Payment refund, initiated by merchant"),
"T1108": _("Fee reversal"),
"T1109": _("Fee refund"),
"T1110": _("Hold for dispute investigation"),
"T1111": _("Cancellation of hold for dispute resolution"),
"T1112": _("MAM reversal"),
"T1113": _("Non-reference credit payment"),
"T1114": _("MassPay reversal transaction"),
"T1115": _("MassPay refund transaction"),
"T1116": _("Instant payment review (IPR) reversal"),
"T1117": _("Rebate or cash back reversal"),
"T1118": _("Generic instrument/Open Wallet reversals (seller side)"),
"T1119": _("Generic instrument/Open Wallet reversals (buyer side)"),
"T1200": _("General account adjustment"),
"T1201": _("Chargeback"),
"T1202": _("Chargeback reversal"),
"T1203": _("Charge-off adjustment"),
"T1204": _("Incentive adjustment"),
"T1205": _("Reimbursement of chargeback"),
"T1207": _("Chargeback re-presentment rejection"),
"T1208": _("Chargeback cancellation"),
"T1300": _("General authorization"),
"T1301": _("Reauthorization"),
"T1302": _("Void of authorization"),
"T1400": _("General dividend"),
"T1500": _("General temporary hold"),
"T1501": _("Account hold for open authorization"),
"T1502": _("Account hold for ACH deposit"),
"T1503": _("Temporary hold on available balance"),
"T1600": _("PayPal buyer credit payment funding"),
"T1601": _("BML credit, transfer from BML"),
"T1602": _("Buyer credit payment"),
"T1603": _("Buyer credit payment withdrawal, transfer to BML"),
"T1700": _("General withdrawal to non-bank institution"),
"T1701": _("WorldLink withdrawal"),
"T1800": _("General buyer credit payment"),
"T1801": _("BML withdrawal, transfer to BML"),
"T1900": _("General adjustment without business-related event"),
"T2000": _("General intra-account transfer"),
"T2001": _("Settlement consolidation"),
"T2002": _("Transfer of funds from payable"),
"T2003": _("Transfer to external GL entity"),
"T2101": _("General hold"),
"T2102": _("General hold release"),
"T2103": _("Reserve hold"),
"T2104": _("Reserve release"),
"T2105": _("Payment review hold"),
"T2106": _("Payment review release"),
"T2107": _("Payment hold"),
"T2108": _("Payment hold release"),
"T2109": _("Gift certificate purchase"),
"T2110": _("Gift certificate redemption"),
"T2111": _("Funds not yet available"),
"T2112": _("Funds available"),
"T2113": _("Blocked payments"),
"T2201": _("Transfer to and from a credit-card-funded restricted balance"),
"T3000": _("Generic instrument/Open Wallet transaction"),
"T5000": _("Deferred disbursement, funds collected for disbursement"),
"T5001": _("Delayed disbursement, funds disbursed"),
"T9700": _("Account receivable for shipping"),
"T9701": _("Funds payable: PayPal-provided funds that must be paid back"),
"T9702": _("Funds receivable: PayPal-provided funds that are being paid back"),
"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):
_inherit = "online.bank.statement.provider"
@api.model
def _get_available_services(self):
return super()._get_available_services() + [
("paypal", "PayPal.com"),
]
def _obtain_statement_data(self, date_since, date_until):
self.ensure_one()
if self.service != "paypal":
return super()._obtain_statement_data(
date_since,
date_until,
) # pragma: no cover
currency = (self.currency_id or self.company_id.currency_id).name
if date_since.tzinfo:
date_since = date_since.astimezone(pytz.utc).replace(tzinfo=None)
if date_until.tzinfo:
date_until = date_until.astimezone(pytz.utc).replace(tzinfo=None)
if date_since < datetime.utcnow() - relativedelta(years=3):
raise UserError(
_(
"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"
)
)
token = self._paypal_get_token()
transactions = self._paypal_get_transactions(
token, currency, date_since, date_until
)
if not transactions:
balance = self._paypal_get_balance(token, currency, date_since)
return [], {"balance_start": balance, "balance_end_real": balance}
# Normalize transactions, sort by date, and get lines
transactions = list(
sorted(
transactions,
key=lambda transaction: self._paypal_get_transaction_date(transaction),
)
)
lines = list(
itertools.chain.from_iterable(
map(lambda x: self._paypal_transaction_to_lines(x), transactions)
)
)
first_transaction = transactions[0]
first_transaction_id = first_transaction["transaction_info"]["transaction_id"]
first_transaction_date = self._paypal_get_transaction_date(first_transaction)
first_transaction = self._paypal_get_transaction(
token, first_transaction_id, first_transaction_date
)
if not first_transaction:
raise UserError(
_("Failed to resolve transaction %s (%s)")
% (first_transaction_id, first_transaction_date)
)
balance_start = self._paypal_get_transaction_ending_balance(first_transaction)
balance_start -= self._paypal_get_transaction_total_amount(first_transaction)
balance_start -= self._paypal_get_transaction_fee_amount(first_transaction)
last_transaction = transactions[-1]
last_transaction_id = last_transaction["transaction_info"]["transaction_id"]
last_transaction_date = self._paypal_get_transaction_date(last_transaction)
last_transaction = self._paypal_get_transaction(
token, last_transaction_id, last_transaction_date
)
if not last_transaction:
raise UserError(
_("Failed to resolve transaction %s (%s)")
% (last_transaction_id, last_transaction_date)
)
balance_end = self._paypal_get_transaction_ending_balance(last_transaction)
return lines, {"balance_start": balance_start, "balance_end_real": balance_end}
@api.model
def _paypal_preparse_transaction(self, transaction):
date = (
dateutil.parser.parse(self._paypal_get_transaction_date(transaction))
.astimezone(pytz.utc)
.replace(tzinfo=None)
)
transaction["transaction_info"]["transaction_updated_date"] = date
return transaction
@api.model
def _paypal_transaction_to_lines(self, data):
transaction = data["transaction_info"]
payer = data["payer_info"]
transaction_id = transaction["transaction_id"]
event_code = transaction["transaction_event_code"]
date = self._paypal_get_transaction_date(data)
total_amount = self._paypal_get_transaction_total_amount(data)
fee_amount = self._paypal_get_transaction_fee_amount(data)
transaction_subject = transaction.get("transaction_subject")
transaction_note = transaction.get("transaction_note")
invoice = transaction.get("invoice_id")
payer_name = payer.get("payer_name", {})
payer_email = payer_name.get("email_address")
if invoice:
invoice = _("Invoice %s") % invoice
note = transaction_id
if transaction_subject or transaction_note:
note = "{}: {}".format(note, transaction_subject or transaction_note)
if payer_email:
note += " (%s)" % payer_email
unique_import_id = "{}-{}".format(transaction_id, int(date.timestamp()))
name = (
invoice
or transaction_subject
or transaction_note
or EVENT_DESCRIPTIONS.get(event_code)
or ""
)
line = {
"ref": name,
"amount": str(total_amount),
"date": date,
"payment_ref": note,
"unique_import_id": unique_import_id,
"online_raw_data": transaction,
}
payer_full_name = payer_name.get("full_name") or payer_name.get(
"alternate_full_name"
)
if payer_full_name:
line.update({"partner_name": payer_full_name})
lines = [line]
if fee_amount:
lines += [
{
"ref": _("Fee for %s") % (name or transaction_id),
"amount": str(fee_amount),
"date": date,
"partner_name": "PayPal",
"unique_import_id": "%s-FEE" % unique_import_id,
"payment_ref": _("Transaction fee for %s") % note,
}
]
return lines
def _paypal_get_token(self):
self.ensure_one()
data = self._paypal_retrieve(
(self.api_base or PAYPAL_API_BASE) + "/v1/oauth2/token",
(self.username, self.password),
data=urlencode({"grant_type": "client_credentials"}).encode("utf-8"),
)
if "scope" not in data or TRANSACTIONS_SCOPE not in data["scope"]:
raise UserError(_("PayPal App features are configured incorrectly!"))
if "token_type" not in data or data["token_type"] != "Bearer":
raise UserError(_("Invalid token type!"))
if "access_token" not in data:
raise UserError(_("Failed to acquire token using Client ID and Secret!"))
return data["access_token"]
def _paypal_get_balance(self, token, currency, as_of_timestamp):
self.ensure_one()
url = (
self.api_base or PAYPAL_API_BASE
) + "/v1/reporting/balances?currency_code={}&as_of_time={}".format(
currency,
as_of_timestamp.isoformat() + "Z",
)
data = self._paypal_retrieve(url, token)
available_balance = data["balances"][0].get("available_balance")
if not available_balance:
return Decimal()
return Decimal(available_balance["value"])
def _paypal_get_transaction(self, token, transaction_id, timestamp):
self.ensure_one()
transaction_date = timestamp.isoformat() + "Z"
url = (
(self.api_base or PAYPAL_API_BASE)
+ "/v1/reporting/transactions"
+ ("?start_date=%s" "&end_date=%s" "&fields=all")
% (
transaction_date,
transaction_date,
)
)
data = self._paypal_retrieve(url, token)
transactions = data["transaction_details"]
for transaction in transactions:
if transaction["transaction_info"]["transaction_id"] != transaction_id:
continue
return transaction
return None
def _paypal_get_transactions(self, token, currency, since, until):
self.ensure_one()
# NOTE: Not more than 31 days in a row
# NOTE: start_date <= date <= end_date, thus check every transaction
interval_step = relativedelta(days=31)
interval_start = since
transactions = []
while interval_start < until:
interval_end = min(interval_start + interval_step, until)
page = 1
total_pages = None
while total_pages is None or page <= total_pages:
url = (
(self.api_base or PAYPAL_API_BASE)
+ "/v1/reporting/transactions"
+ (
"?transaction_currency=%s"
"&start_date=%s"
"&end_date=%s"
"&fields=all"
"&balance_affecting_records_only=Y"
"&page_size=500"
"&page=%d"
% (
currency,
interval_start.isoformat() + "Z",
interval_end.isoformat() + "Z",
page,
)
)
)
# NOTE: Workaround for INVALID_REQUEST (see ROADMAP.rst)
invalid_data_workaround = self.env.context.get(
"test_account_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),
data["transaction_details"],
)
transactions += list(
filter(
lambda transaction: interval_start
<= self._paypal_get_transaction_date(transaction)
< interval_end,
interval_transactions,
)
)
total_pages = data["total_pages"]
page += 1
interval_start += interval_step
return transactions
@api.model
def _paypal_get_transaction_date(self, transaction):
# NOTE: CSV reports from PayPal use this date, search as well
return transaction["transaction_info"]["transaction_updated_date"]
@api.model
def _paypal_get_transaction_total_amount(self, transaction):
transaction_amount = transaction["transaction_info"].get("transaction_amount")
if not transaction_amount:
return Decimal()
return Decimal(transaction_amount["value"])
@api.model
def _paypal_get_transaction_fee_amount(self, transaction):
fee_amount = transaction["transaction_info"].get("fee_amount")
if not fee_amount:
return Decimal()
return Decimal(fee_amount["value"])
@api.model
def _paypal_get_transaction_ending_balance(self, transaction):
# NOTE: 'available_balance' instead of 'ending_balance' as per CSV file
transaction_amount = transaction["transaction_info"].get("available_balance")
if not transaction_amount:
return Decimal()
return Decimal(transaction_amount["value"])
@api.model
def _paypal_decode_error(self, content):
if "name" in content:
return UserError(
"%s: %s"
% (
content["name"],
content.get("message", _("Unknown error")),
)
)
if "error" in content:
return UserError(
"%s: %s"
% (
content["error"],
content.get("error_description", _("Unknown error")),
)
)
return None
@api.model
def _paypal_retrieve(self, url, auth, data=None):
try:
with self._paypal_urlopen(url, auth, data) as response:
content = response.read().decode("utf-8")
except HTTPError as e:
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):
if not auth:
raise UserError(_("No authentication specified!"))
request = urllib.request.Request(url, data=data)
if isinstance(auth, tuple):
request.add_header(
"Authorization",
"Basic %s"
% str(
b64encode(("{}:{}".format(auth[0], auth[1])).encode("utf-8")),
"utf-8",
),
)
elif isinstance(auth, str):
request.add_header("Authorization", "Bearer %s" % auth)
else:
raise UserError(_("Unknown authentication specified!"))
return urllib.request.urlopen(request)

View File

@@ -0,0 +1,29 @@
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 *PayPal.com* 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 *PayPal.com* 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 *Client ID* and *Secret*:
#. Open `PayPal Developer <https://developer.paypal.com/developer/applications/>`_
#. Go to *My Apps & Credentials* and switch to *Live*
#. Under *REST API apps*, click *Create App* to create new application (e.g. *Odoo*)
#. Copy *Client ID* and *Secret* to use during provider configuration
#. Under *Live App Settings*, uncheck all features except *Transaction Search*
#. Click Save

View File

@@ -0,0 +1,4 @@
* `CorporateHub <https://corporatehub.eu/>`__
* Alexey Pelykh <alexey.pelykh@corphub.eu>
* Omar Castiñeira <omar@comunitea.com>

View File

@@ -0,0 +1,2 @@
This module provides online bank statements from
`PayPal.com <https://paypal.com/>`__.

View File

@@ -0,0 +1,11 @@
* Only transactions for the previous three years are retrieved, historical data
can be imported manually, see ``account_bank_statement_import_paypal``. See
`PayPal Help Center article <https://www.paypal.com/us/smarthelp/article/why-can't-i-access-transaction-history-greater-than-3-years-ts2241>`_
for details.
* `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).

View File

@@ -0,0 +1,6 @@
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*

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,487 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
<title>Online Bank Statements: PayPal.com</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="online-bank-statements-paypal-com">
<h1 class="title">Online Bank Statements: PayPal.com</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/bank-statement-import/tree/14.0/account_statement_import_online_paypal"><img alt="OCA/bank-statement-import" src="https://img.shields.io/badge/github-OCA%2Fbank--statement--import-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/bank-statement-import-14-0/bank-statement-import-14-0-account_statement_import_online_paypal"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/174/14.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This module provides online bank statements from
<a class="reference external" href="https://paypal.com/">PayPal.com</a>.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id3">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id1">Configuration</a></h1>
<p>To configure online bank statements provider:</p>
<ol class="arabic simple">
<li>Go to <em>Invoicing &gt; Configuration &gt; Bank Accounts</em></li>
<li>Open bank account to configure and edit it</li>
<li>Set <em>Bank Feeds</em> to <em>Online</em></li>
<li>Select <em>PayPal.com</em> as online bank statements provider in
<em>Online Bank Statements (OCA)</em> section</li>
<li>Save the bank account</li>
<li>Click on provider and configure provider-specific settings.</li>
</ol>
<p>or, alternatively:</p>
<ol class="arabic simple">
<li>Go to <em>Invoicing &gt; Overview</em></li>
<li>Open settings of the corresponding journal account</li>
<li>Switch to <em>Bank Account</em> tab</li>
<li>Set <em>Bank Feeds</em> to <em>Online</em></li>
<li>Select <em>PayPal.com</em> as online bank statements provider in
<em>Online Bank Statements (OCA)</em> section</li>
<li>Save the bank account</li>
<li>Click on provider and configure provider-specific settings.</li>
</ol>
<p>To obtain <em>Client ID</em> and <em>Secret</em>:</p>
<ol class="arabic simple">
<li>Open <a class="reference external" href="https://developer.paypal.com/developer/applications/">PayPal Developer</a></li>
<li>Go to <em>My Apps &amp; Credentials</em> and switch to <em>Live</em></li>
<li>Under <em>REST API apps</em>, click <em>Create App</em> to create new application (e.g. <em>Odoo</em>)</li>
<li>Copy <em>Client ID</em> and <em>Secret</em> to use during provider configuration</li>
<li>Under <em>Live App Settings</em>, uncheck all features except <em>Transaction Search</em></li>
<li>Click Save</li>
</ol>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
<p>To pull historical bank statements:</p>
<ol class="arabic simple">
<li>Go to <em>Invoicing &gt; Configuration &gt; Bank Accounts</em></li>
<li>Select specific bank accounts</li>
<li>Launch <em>Actions &gt; Online Bank Statements Pull Wizard</em></li>
<li>Configure date interval and click <em>Pull</em></li>
</ol>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#id3">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>Only transactions for the previous three years are retrieved, historical data
can be imported manually, see <tt class="docutils literal">account_bank_statement_import_paypal</tt>. See
<a class="reference external" href="https://www.paypal.com/us/smarthelp/article/why-can't-i-access-transaction-history-greater-than-3-years-ts2241">PayPal Help Center article</a>
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>Theres 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">
<h1><a class="toc-backref" href="#id4">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/bank-statement-import/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/bank-statement-import/issues/new?body=module:%20account_statement_import_online_paypal%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id5">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id6">Authors</a></h2>
<ul class="simple">
<li>CorporateHub</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id7">Contributors</a></h2>
<ul class="simple">
<li><a class="reference external" href="https://corporatehub.eu/">CorporateHub</a><ul>
<li>Alexey Pelykh &lt;<a class="reference external" href="mailto:alexey.pelykh&#64;corphub.eu">alexey.pelykh&#64;corphub.eu</a>&gt;</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id8">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>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.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external" href="https://github.com/alexey-pelykh"><img alt="alexey-pelykh" src="https://github.com/alexey-pelykh.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/bank-statement-import/tree/14.0/account_statement_import_online_paypal">OCA/bank-statement-import</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import test_account_statement_import_online_paypal

View File

@@ -0,0 +1,808 @@
# 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).
import json
from datetime import datetime
from decimal import Decimal
from unittest import mock
from urllib.error import HTTPError
from dateutil.relativedelta import relativedelta
from odoo import fields
from odoo.exceptions import UserError
from odoo.tests import common
_module_ns = "odoo.addons.account_statement_import_online_paypal"
_provider_class = (
_module_ns
+ ".models.online_bank_statement_provider_paypal"
+ ".OnlineBankStatementProviderPayPal"
)
class FakeHTTPError(HTTPError):
def __init__(self, content):
self.content = content
# pylint: disable=method-required-super
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, exc_type, exc_value, traceback):
pass
# pylint: disable=method-required-super
def read(self):
if self.throw:
raise FakeHTTPError(self.content)
return self.content.encode("utf-8")
class TestAccountBankAccountStatementImportOnlinePayPal(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.OnlineBankStatementProvider = self.env["online.bank.statement.provider"]
self.AccountBankStatement = self.env["account.bank.statement"]
self.AccountBankStatementLine = self.env["account.bank.statement.line"]
Provider = self.OnlineBankStatementProvider
self.paypal_parse_transaction = lambda payload: (
Provider._paypal_transaction_to_lines(
Provider._paypal_preparse_transaction(
json.loads(
payload,
parse_float=Decimal,
)
)
)
)
self.mock_token = lambda: mock.patch(
_provider_class + "._paypal_get_token",
return_value="--TOKEN--",
)
def test_good_token(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 = json.loads(
"""{
"scope": "https://uri.paypal.com/services/reporting/search/read",
"access_token": "---TOKEN---",
"token_type": "Bearer",
"app_id": "APP-1234567890",
"expires_in": 32400,
"nonce": "---NONCE---"
}""",
parse_float=Decimal,
)
token = None
with mock.patch(
_provider_class + "._paypal_retrieve",
return_value=mocked_response,
):
token = provider._paypal_get_token()
self.assertEqual(token, "---TOKEN---")
def test_bad_token_scope(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 = json.loads(
"""{
"scope": "openid https://uri.paypal.com/services/applications/webhooks",
"access_token": "---TOKEN---",
"token_type": "Bearer",
"app_id": "APP-1234567890",
"expires_in": 32400,
"nonce": "---NONCE---"
}""",
parse_float=Decimal,
)
with mock.patch(
_provider_class + "._paypal_retrieve",
return_value=mocked_response,
):
with self.assertRaises(Exception):
provider._paypal_get_token()
def test_bad_token_type(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 = json.loads(
"""{
"scope": "https://uri.paypal.com/services/reporting/search/read",
"access_token": "---TOKEN---",
"token_type": "NotBearer",
"app_id": "APP-1234567890",
"expires_in": 32400,
"nonce": "---NONCE---"
}""",
parse_float=Decimal,
)
with mock.patch(
_provider_class + "._paypal_retrieve",
return_value=mocked_response,
):
with self.assertRaises(Exception):
provider._paypal_get_token()
def test_no_token(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 = json.loads(
"""{
"scope": "https://uri.paypal.com/services/reporting/search/read",
"token_type": "Bearer",
"app_id": "APP-1234567890",
"expires_in": 32400,
"nonce": "---NONCE---"
}""",
parse_float=Decimal,
)
with mock.patch(
_provider_class + "._paypal_retrieve",
return_value=mocked_response,
):
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_1 = UrlopenRetValMock(
"""{
"debug_id": "eec890ebd5798",
"details": "xxxxxx",
"links": "xxxxxx",
"message": "Data for the given start date is not available.",
"name": "INVALID_REQUEST"
}""",
throw=True,
)
mocked_response_2 = UrlopenRetValMock(
"""{
"balances": [
{
"currency": "EUR",
"primary": true,
"total_balance": {
"currency_code": "EUR",
"value": "0.75"
},
"available_balance": {
"currency_code": "EUR",
"value": "0.75"
},
"withheld_balance": {
"currency_code": "EUR",
"value": "0.00"
}
}
],
"account_id": "1234567890",
"as_of_time": "2019-08-01T00:00:00+0000",
"last_refresh_time": "2019-08-01T00:00:00+0000"
}"""
)
with mock.patch(
_provider_class + "._paypal_urlopen",
side_effect=[mocked_response_1, mocked_response_2],
), self.mock_token():
data = provider.with_context(
test_account_statement_import_online_paypal_monday=True,
)._obtain_statement_data(
self.now - relativedelta(hours=1),
self.now,
)
self.assertEqual(data, ([], {"balance_start": 0.75, "balance_end_real": 0.75}))
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": "MESSAGE",
"name": "ERROR"
}""",
throw=True,
)
with mock.patch(
_provider_class + "._paypal_urlopen",
return_value=mocked_response,
):
with self.assertRaises(UserError):
provider._paypal_retrieve("https://url", "")
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": "ERROR DESCRIPTION",
"error": "ERROR"
}""",
throw=True,
)
with mock.patch(
_provider_class + "._paypal_urlopen",
return_value=mocked_response,
):
with self.assertRaises(UserError):
provider._paypal_retrieve("https://url", "")
def test_empty_pull(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_1 = json.loads(
"""{
"transaction_details": [],
"account_number": "1234567890",
"start_date": "2019-08-01T00:00:00+0000",
"end_date": "2019-08-01T00:00:00+0000",
"last_refreshed_datetime": "2019-09-01T00:00:00+0000",
"page": 1,
"total_items": 0,
"total_pages": 0
}""",
parse_float=Decimal,
)
mocked_response_2 = json.loads(
"""{
"balances": [
{
"currency": "EUR",
"primary": true,
"total_balance": {
"currency_code": "EUR",
"value": "0.75"
},
"available_balance": {
"currency_code": "EUR",
"value": "0.75"
},
"withheld_balance": {
"currency_code": "EUR",
"value": "0.00"
}
}
],
"account_id": "1234567890",
"as_of_time": "2019-08-01T00:00:00+0000",
"last_refresh_time": "2019-08-01T00:00:00+0000"
}""",
parse_float=Decimal,
)
with mock.patch(
_provider_class + "._paypal_retrieve",
side_effect=[mocked_response_1, mocked_response_2],
), self.mock_token():
data = provider._obtain_statement_data(
self.now - relativedelta(hours=1),
self.now,
)
self.assertEqual(data, ([], {"balance_start": 0.75, "balance_end_real": 0.75}))
def test_ancient_pull(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 = json.loads(
"""{
"transaction_details": [],
"account_number": "1234567890",
"start_date": "2019-08-01T00:00:00+0000",
"end_date": "2019-08-01T00:00:00+0000",
"last_refreshed_datetime": "2019-09-01T00:00:00+0000",
"page": 1,
"total_items": 0,
"total_pages": 0
}""",
parse_float=Decimal,
)
with mock.patch(
_provider_class + "._paypal_retrieve",
return_value=mocked_response,
), self.mock_token():
with self.assertRaises(Exception):
provider._obtain_statement_data(
self.now - relativedelta(years=5),
self.now,
)
def test_pull(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 = json.loads(
"""{
"transaction_details": [{
"transaction_info": {
"paypal_account_id": "1234567890",
"transaction_id": "1234567890",
"transaction_event_code": "T1234",
"transaction_initiation_date": "2019-08-01T00:00:00+0000",
"transaction_updated_date": "2019-08-01T00:00:00+0000",
"transaction_amount": {
"currency_code": "USD",
"value": "1000.00"
},
"fee_amount": {
"currency_code": "USD",
"value": "-100.00"
},
"transaction_status": "S",
"transaction_subject": "Payment for Invoice(s) 1",
"ending_balance": {
"currency_code": "USD",
"value": "900.00"
},
"available_balance": {
"currency_code": "USD",
"value": "900.00"
},
"invoice_id": "1"
},
"payer_info": {
"account_id": "1234567890",
"email_address": "partner@example.com",
"address_status": "Y",
"payer_status": "N",
"payer_name": {
"alternate_full_name": "Acme, Inc."
},
"country_code": "US"
},
"shipping_info": {},
"cart_info": {},
"store_info": {},
"auction_info": {},
"incentive_info": {}
}, {
"transaction_info": {
"paypal_account_id": "1234567890",
"transaction_id": "1234567891",
"transaction_event_code": "T1234",
"transaction_initiation_date": "2019-08-02T00:00:00+0000",
"transaction_updated_date": "2019-08-02T00:00:00+0000",
"transaction_amount": {
"currency_code": "USD",
"value": "1000.00"
},
"fee_amount": {
"currency_code": "USD",
"value": "-100.00"
},
"transaction_status": "S",
"transaction_subject": "Payment for Invoice(s) 1",
"ending_balance": {
"currency_code": "USD",
"value": "900.00"
},
"available_balance": {
"currency_code": "USD",
"value": "900.00"
},
"invoice_id": "1"
},
"payer_info": {
"account_id": "1234567890",
"email_address": "partner@example.com",
"address_status": "Y",
"payer_status": "N",
"payer_name": {
"alternate_full_name": "Acme, Inc."
},
"country_code": "US"
},
"shipping_info": {},
"cart_info": {},
"store_info": {},
"auction_info": {},
"incentive_info": {}
}],
"account_number": "1234567890",
"start_date": "2019-08-01T00:00:00+0000",
"end_date": "2019-08-02T00:00:00+0000",
"last_refreshed_datetime": "2019-09-01T00:00:00+0000",
"page": 1,
"total_items": 1,
"total_pages": 1
}""",
parse_float=Decimal,
)
with mock.patch(
_provider_class + "._paypal_retrieve",
return_value=mocked_response,
), self.mock_token():
data = provider._obtain_statement_data(
datetime(2019, 8, 1),
datetime(2019, 8, 2),
)
self.assertEqual(len(data[0]), 2)
del data[0][0]["online_raw_data"]
self.assertEqual(
data[0][0],
{
"date": datetime(2019, 8, 1),
"amount": "1000.00",
"ref": "Invoice 1",
"payment_ref": "1234567890: Payment for Invoice(s) 1",
"partner_name": "Acme, Inc.",
"unique_import_id": "1234567890-1564617600",
},
)
self.assertEqual(
data[0][1],
{
"date": datetime(2019, 8, 1),
"amount": "-100.00",
"ref": "Fee for Invoice 1",
"payment_ref": "Transaction fee for 1234567890: Payment for Invoice(s) 1",
"partner_name": "PayPal",
"unique_import_id": "1234567890-1564617600-FEE",
},
)
self.assertEqual(data[1], {"balance_start": 0.0, "balance_end_real": 900.0})
def test_transaction_parse_1(self):
lines = self.paypal_parse_transaction(
"""{
"transaction_info": {
"paypal_account_id": "1234567890",
"transaction_id": "1234567890",
"transaction_event_code": "T1234",
"transaction_initiation_date": "2019-08-01T00:00:00+0000",
"transaction_updated_date": "2019-08-01T00:00:00+0000",
"transaction_amount": {
"currency_code": "USD",
"value": "1000.00"
},
"fee_amount": {
"currency_code": "USD",
"value": "0.00"
},
"transaction_status": "S",
"transaction_subject": "Payment for Invoice(s) 1",
"ending_balance": {
"currency_code": "USD",
"value": "1000.00"
},
"available_balance": {
"currency_code": "USD",
"value": "1000.00"
},
"invoice_id": "1"
},
"payer_info": {
"account_id": "1234567890",
"email_address": "partner@example.com",
"address_status": "Y",
"payer_status": "N",
"payer_name": {
"alternate_full_name": "Acme, Inc."
},
"country_code": "US"
},
"shipping_info": {},
"cart_info": {},
"store_info": {},
"auction_info": {},
"incentive_info": {}
}"""
)
self.assertEqual(len(lines), 1)
del lines[0]["online_raw_data"]
self.assertEqual(
lines[0],
{
"date": datetime(2019, 8, 1),
"amount": "1000.00",
"ref": "Invoice 1",
"payment_ref": "1234567890: Payment for Invoice(s) 1",
"partner_name": "Acme, Inc.",
"unique_import_id": "1234567890-1564617600",
},
)
def test_transaction_parse_2(self):
lines = self.paypal_parse_transaction(
"""{
"transaction_info": {
"paypal_account_id": "1234567890",
"transaction_id": "1234567890",
"transaction_event_code": "T1234",
"transaction_initiation_date": "2019-08-01T00:00:00+0000",
"transaction_updated_date": "2019-08-01T00:00:00+0000",
"transaction_amount": {
"currency_code": "USD",
"value": "1000.00"
},
"fee_amount": {
"currency_code": "USD",
"value": "0.00"
},
"transaction_status": "S",
"transaction_subject": "Payment for Invoice(s) 1",
"ending_balance": {
"currency_code": "USD",
"value": "1000.00"
},
"available_balance": {
"currency_code": "USD",
"value": "1000.00"
},
"invoice_id": "1"
},
"payer_info": {
"account_id": "1234567890",
"email_address": "partner@example.com",
"address_status": "Y",
"payer_status": "N",
"payer_name": {
"alternate_full_name": "Acme, Inc."
},
"country_code": "US"
},
"shipping_info": {},
"cart_info": {},
"store_info": {},
"auction_info": {},
"incentive_info": {}
}"""
)
self.assertEqual(len(lines), 1)
del lines[0]["online_raw_data"]
self.assertEqual(
lines[0],
{
"date": datetime(2019, 8, 1),
"amount": "1000.00",
"ref": "Invoice 1",
"payment_ref": "1234567890: Payment for Invoice(s) 1",
"partner_name": "Acme, Inc.",
"unique_import_id": "1234567890-1564617600",
},
)
def test_transaction_parse_3(self):
lines = self.paypal_parse_transaction(
"""{
"transaction_info": {
"paypal_account_id": "1234567890",
"transaction_id": "1234567890",
"transaction_event_code": "T1234",
"transaction_initiation_date": "2019-08-01T00:00:00+0000",
"transaction_updated_date": "2019-08-01T00:00:00+0000",
"transaction_amount": {
"currency_code": "USD",
"value": "1000.00"
},
"fee_amount": {
"currency_code": "USD",
"value": "-100.00"
},
"transaction_status": "S",
"transaction_subject": "Payment for Invoice(s) 1",
"ending_balance": {
"currency_code": "USD",
"value": "900.00"
},
"available_balance": {
"currency_code": "USD",
"value": "900.00"
},
"invoice_id": "1"
},
"payer_info": {
"account_id": "1234567890",
"email_address": "partner@example.com",
"address_status": "Y",
"payer_status": "N",
"payer_name": {
"alternate_full_name": "Acme, Inc."
},
"country_code": "US"
},
"shipping_info": {},
"cart_info": {},
"store_info": {},
"auction_info": {},
"incentive_info": {}
}"""
)
self.assertEqual(len(lines), 2)
del lines[0]["online_raw_data"]
self.assertEqual(
lines[0],
{
"date": datetime(2019, 8, 1),
"amount": "1000.00",
"ref": "Invoice 1",
"payment_ref": "1234567890: Payment for Invoice(s) 1",
"partner_name": "Acme, Inc.",
"unique_import_id": "1234567890-1564617600",
},
)
self.assertEqual(
lines[1],
{
"date": datetime(2019, 8, 1),
"amount": "-100.00",
"ref": "Fee for Invoice 1",
"payment_ref": "Transaction fee for 1234567890: Payment for Invoice(s) 1",
"partner_name": "PayPal",
"unique_import_id": "1234567890-1564617600-FEE",
},
)
def test_transaction_parse_4(self):
lines = self.paypal_parse_transaction(
"""{
"transaction_info": {
"paypal_account_id": "1234567890",
"transaction_id": "1234567890",
"transaction_event_code": "T1234",
"transaction_initiation_date": "2019-08-01T00:00:00+0000",
"transaction_updated_date": "2019-08-01T00:00:00+0000",
"transaction_amount": {
"currency_code": "USD",
"value": "1000.00"
},
"transaction_status": "S",
"transaction_subject": "Payment for Invoice(s) 1",
"ending_balance": {
"currency_code": "USD",
"value": "1000.00"
},
"available_balance": {
"currency_code": "USD",
"value": "1000.00"
},
"invoice_id": "1"
},
"payer_info": {
"account_id": "1234567890",
"email_address": "partner@example.com",
"address_status": "Y",
"payer_status": "N",
"payer_name": {
"alternate_full_name": "Acme, Inc."
},
"country_code": "US"
},
"shipping_info": {},
"cart_info": {},
"store_info": {},
"auction_info": {},
"incentive_info": {}
}"""
)
self.assertEqual(len(lines), 1)
del lines[0]["online_raw_data"]
self.assertEqual(
lines[0],
{
"date": datetime(2019, 8, 1),
"amount": "1000.00",
"ref": "Invoice 1",
"payment_ref": "1234567890: Payment for Invoice(s) 1",
"partner_name": "Acme, Inc.",
"unique_import_id": "1234567890-1564617600",
},
)

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2019 Brainbean Apps (https://brainbeanapps.com)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo>
<record model="ir.ui.view" id="online_bank_statement_provider_form">
<field name="name">online.bank.statement.provider.form</field>
<field name="model">online.bank.statement.provider</field>
<field
name="inherit_id"
ref="account_statement_import_online.online_bank_statement_provider_form"
/>
<field name="arch" type="xml">
<xpath expr="//group[@name='main']" position="inside">
<group
name="paypal"
attrs="{'invisible': [('service', '!=', 'paypal')]}"
>
<group>
<field
name="api_base"
string="API base"
groups="base.group_no_one"
/>
<field
name="username"
string="Client ID"
password="True"
attrs="{'required': [('service', '=', 'paypal')]}"
/>
<field
name="password"
string="Secret"
password="True"
attrs="{'required': [('service', '=', 'paypal')]}"
/>
</group>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
../../../../account_statement_import_online_paypal

View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)