Skip to content

Commit

Permalink
[ADD]account_payment_plaid
Browse files Browse the repository at this point in the history
  • Loading branch information
adasatorres committed Jul 8, 2024
1 parent 1d814c3 commit 4b82e99
Show file tree
Hide file tree
Showing 41 changed files with 13,095 additions and 0 deletions.
108 changes: 108 additions & 0 deletions account_payment_plaid/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
=====================
Account Payment Plaid
=====================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:815525549c46556e7fa6a37a49653162624972fbba8a8888ed314f8841007cba
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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--payment-lightgray.png?logo=github
:target: https://github.com/OCA/bank-payment/tree/14.0/account_payment_plaid
:alt: OCA/bank-payment
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/bank-payment-14-0/bank-payment-14-0-account_payment_plaid
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/bank-payment&target_branch=14.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This addon allow pay your bills with plaid platform using the ACH transfer method.
Do you need plaid account for use this module.

**Table of contents**

.. contents::
:local:

Usage
=====

Using the Plaid API for bank transfers on production or development environment:

#. Go to "Settings" > "Plaid".
#. Add your Plaid credentials.
#. Synchronize with Plaid.
#. Select your bank account.
#. Go to "Contacts" and add the plaid client id for the contact.
#. Go to your invoice and click on "Pay with Plaid" button.\nYou can see the button if the bill is confirmed.\nWhen you click on the button, you will see a confirmation window.
#. When you confirm the payment and the confirmation window\nis closed you can see the transfer in "Settings" > "Technical" > "Plaid" > "Transfer".
#. When the transfer is done, you can see the bill as paid and the payment create on odoo.

If you are using the sandbox environment for testing,
do you need to use the simulation method for simulate the transfer.
This method only works with the sandbox environment.
You can found this method on "Settings" > "Technical" > "Plaid" > "Transfer".

#. Select the transfer that you want to simulate.
#. Click on "Simulate Transfer" button.
#. Select the command that you want to simulate.
* "Simulate Transfer" : This command create a event on sandbox environment.\nDo you need this event for check the status of the transfer on Odoo.
* "Simulate transfer ledger available" : This command simulate converting pending balance\nto available balance for all originators in the Sandbox environment.
#. Click on "Confirm" button.

This addon use the cron for check the status of the transfer on Plaid and update the bill and payment.
If you need more information about Plaid, please visit the `Plaid website <https://plaid.com>`_ or `Plaid Docs Transfer <https://plaid.com/docs/transfer/>`_.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/bank-payment/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/bank-payment/issues/new?body=module:%20account_payment_plaid%0Aversion:%2014.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
~~~~~~~

* Binhex

Contributors
~~~~~~~~~~~~

* `Binhex <https://binhex.cloud>_`

* Adasat Torres de León <[email protected]>

Maintainers
~~~~~~~~~~~

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/bank-payment <https://github.com/OCA/bank-payment/tree/14.0/account_payment_plaid>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
2 changes: 2 additions & 0 deletions account_payment_plaid/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import wizard
from . import models
27 changes: 27 additions & 0 deletions account_payment_plaid/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2024 Binhex - Adasat Torres de León.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Account Payment Plaid",
"version": "14.0.1.0.0",
"category": "Connector",
"website": "https://github.com/OCA/bank-payment",
"author": "Binhex, Odoo Community Association (OCA)",
"license": "AGPL-3",
"depends": ["base", "account", "purchase"],
"data": [
"security/ir.model.access.csv",
"views/assets.xml",
"views/account_move_views.xml",
"views/res_partner_views.xml",
"views/res_config_settings_views.xml",
"views/plaid_account_views.xml",
"views/plaid_transfer_views.xml",
"wizard/account_payment_plaid_wizard_views.xml",
"wizard/plaid_transfer_sandbox_simulation_wizard_views.xml",
"data/sync_transfer_events_cron.xml",
"data/payment_method_data.xml",
],
"external_dependencies": {
"python": ["plaid-python"],
},
}
10 changes: 10 additions & 0 deletions account_payment_plaid/data/payment_method_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<record id="account_payment_method_plaid_out" model="account.payment.method">
<field name="name">Plaid</field>
<field name="code">plaid</field>
<field name="payment_type">outbound</field>
</record>
</data>
</odoo>
16 changes: 16 additions & 0 deletions account_payment_plaid/data/sync_transfer_events_cron.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<record id="plaid_sync_transfer_cron" model="ir.cron">
<field name="name">Sync Transfer Events</field>
<field name="model_id" ref="model_plaid_transfer" />
<field name="interval_number">1</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="state">code</field>
<field name="code">
model.cron_sync_transfer_events()
</field>
</record>
</data>
</odoo>
7 changes: 7 additions & 0 deletions account_payment_plaid/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from . import plaid_interface
from . import plaid_account
from . import plaid_transfer
from . import res_company
from . import res_config_settings
from . import res_partner
from . import account_move
24 changes: 24 additions & 0 deletions account_payment_plaid/models/account_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from odoo import _, models


class AccountMove(models.Model):

_inherit = "account.move"

def action_payment_with_plaid_wizard(self):
return {
"type": "ir.actions.act_window",
"name": _("Payment with Plaid"),
"res_model": "account.payment.plaid.wizard",
"view_mode": "form",
"view_type": "form",
"context": {
"default_partner_id": self.partner_id.id,
"default_account_move_id": self.id,
"default_amount": self.amount_total,
"default_currency_id": self.currency_id.id,
"default_description": self.name,
"default_company_id": self.company_id.id,
},
"target": "new",
}
16 changes: 16 additions & 0 deletions account_payment_plaid/models/plaid_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from odoo import fields, models


class PlaidAccount(models.Model):
_name = "plaid.account"

name = fields.Char(string="Name", required=True)
account = fields.Char(string="Account ID", required=True)
currency_id = fields.Many2one("res.currency", string="Currency")
official_name = fields.Char(string="Official Name")
type = fields.Char(string="Type")
mask = fields.Char(string="Mask")
subtype = fields.Char(string="Subtype")
company_id = fields.Many2one(
comodel_name="res.company", default=lambda self: self.env.company.id
)
172 changes: 172 additions & 0 deletions account_payment_plaid/models/plaid_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Copyright 2024 Binhex - Adasat Torres de León.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

import logging

from odoo import _, models
from odoo.exceptions import ValidationError

_logger = logging.getLogger(__name__)
try:
import plaid
from plaid.api import plaid_api
from plaid.model.accounts_get_request import AccountsGetRequest
from plaid.model.ach_class import ACHClass
from plaid.model.country_code import CountryCode
from plaid.model.item_public_token_exchange_request import (
ItemPublicTokenExchangeRequest,
)
from plaid.model.link_token_create_request import LinkTokenCreateRequest
from plaid.model.link_token_create_request_user import LinkTokenCreateRequestUser
from plaid.model.products import Products
from plaid.model.sandbox_transfer_simulate_request import (
SandboxTransferSimulateRequest,
)
from plaid.model.transfer_authorization_create_request import (
TransferAuthorizationCreateRequest,
)
from plaid.model.transfer_authorization_user_in_request import (
TransferAuthorizationUserInRequest,
)
from plaid.model.transfer_create_request import TransferCreateRequest
from plaid.model.transfer_event_sync_request import TransferEventSyncRequest
from plaid.model.transfer_failure import TransferFailure
from plaid.model.transfer_network import TransferNetwork
from plaid.model.transfer_type import TransferType
except (ImportError, IOError) as err:
_logger.debug(err)


class PlaidInterface(models.AbstractModel):
_name = "plaid.interface"
_description = "Plaid Interface"

def _get_host(self, host):
if host == "devel":
return plaid.Environment.Development
if host == "sand":
return plaid.Environment.Sandbox
if host == "prod":
return plaid.Environment.Production
return False

def _client(self, client_id, secret, host):
configuration = plaid.Configuration(
host=self._get_host(host),
api_key={
"clientId": client_id or "",
"secret": secret or "",
},
)
try:
return plaid_api.PlaidApi(plaid.ApiClient(configuration))
except plaid.ApiException as e:
raise ValidationError(_("Error getting client api: %s") % e.body) from e

def _link(self, client, language, country_code, company_name, products):
request = LinkTokenCreateRequest(
products=[Products(product) for product in products],
client_name=company_name,
country_codes=[CountryCode(country_code)],
language=language,
user=LinkTokenCreateRequestUser(client_user_id="client"),
)
try:
response = client.link_token_create(request)
except plaid.ApiException as e:
raise ValidationError(_("Error getting link token: %s") % e.body) from e
return response.to_dict()["link_token"]

def _login(self, client, public_token):
request = ItemPublicTokenExchangeRequest(public_token=public_token)
try:
response = client.item_public_token_exchange(request)
except plaid.ApiException as e:
raise ValidationError(_("Error getting access token: %s") % e.body) from e
return response["access_token"]

def _get_accounts(self, client, access_token):
request = AccountsGetRequest(access_token=access_token)
try:
response = client.accounts_get(request)
except plaid.ApiException as e:
raise ValidationError(_("Error getting accounts: %s") % e.body) from e
return response.to_dict()["accounts"]

def _transfer_auth(self, client, access_token, account_id, partner_id, amount):
request = TransferAuthorizationCreateRequest(
access_token=access_token,
account_id=account_id,
originator_client_id=partner_id.plaid_client_id,
type=TransferType("credit"),
amount=amount,
network=TransferNetwork("ach"),
user=TransferAuthorizationUserInRequest(legal_name=partner_id.name),
ach_class=ACHClass("ppd"),
)

try:
response = client.transfer_authorization_create(request)
except plaid.ApiException as e:
raise ValidationError(_("Error getting transfer auth: %s") % e.body) from e
response_dict = response.to_dict()["authorization"]
return {
"authorization_id": response_dict["id"],
"decision": response_dict["decision"],
"decision_rationale": response_dict["decision_rationale"],
}

def _transfer(
self, client, access_token, account_id, authorization_id, description
):
request = TransferCreateRequest(
access_token=access_token,
account_id=account_id,
authorization_id=authorization_id,
description=description,
)
try:
response = client.transfer_create(request)
except plaid.ApiException as e:
raise ValidationError(_("Error creating transfer: %s") % e.body) from e
return response.to_dict()["transfer"]

def _sync_transfer_events(self, client):
request = TransferEventSyncRequest(after_id=4, count=25)
try:
response = client.transfer_event_sync(request)
except plaid.ApiException as e:
raise ValidationError(
_("Error syncing transfer events: %s") % e.body
) from e
return response.to_dict()["transfer_events"]

############################
# Sandbox Transfer Methods #
############################

def _sandbox_simulate_transfer(self, client, transfer_id, event_type, failure):
if event_type == "failed":
request = SandboxTransferSimulateRequest(
transfer_id=transfer_id,
event_type=event_type,
failure_reason=TransferFailure(
description=failure,
),
)
else:
request = SandboxTransferSimulateRequest(
transfer_id=transfer_id,
event_type=event_type,
)

try:
client.sandbox_transfer_simulate(request)
except plaid.ApiException as e:
raise ValidationError(_("Error simulating transfer: %s") % e.body) from e

def _sandbox_transfer_ledger_simulate_available(self, client):
try:
client.sandbox_transfer_ledger_simulate_available({})
except plaid.ApiException as e:
raise ValidationError(_("Error simulating transfer: %s") % e.body) from e
Loading

0 comments on commit 4b82e99

Please sign in to comment.