Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use Plaid's new API (develop) #23318

Merged
merged 3 commits into from Sep 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 21 additions & 17 deletions erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
Expand Up @@ -72,7 +72,7 @@ erpnext.accounts.bankReconciliation = class BankReconciliation {
check_plaid_status() {
const me = this;
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
if (r && r.enabled == "1") {
if (r && r.enabled === "1") {
me.plaid_status = "active"
} else {
me.plaid_status = "inactive"
Expand Down Expand Up @@ -139,7 +139,7 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
}

make() {
const me = this;
const me = this;
new frappe.ui.FileUploader({
method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
allow_multiple: 0,
Expand Down Expand Up @@ -214,31 +214,35 @@ erpnext.accounts.bankTransactionSync = class bankTransactionSync {

init_config() {
const me = this;
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
.then(result => {
me.plaid_env = result.plaid_env;
me.plaid_public_key = result.plaid_public_key;
me.client_name = result.client_name;
me.sync_transactions()
})
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_plaid_configuration')
.then(result => {
me.plaid_env = result.plaid_env;
me.client_name = result.client_name;
me.link_token = result.link_token;
me.sync_transactions();
})
}

sync_transactions() {
const me = this;
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (v) => {
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (r) => {
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
bank: v['bank'],
bank: r.bank,
bank_account: me.parent.bank_account,
freeze: true
})
.then((result) => {
let result_title = (result.length > 0) ? __("{0} bank transaction(s) created", [result.length]) : __("This bank account is already synchronized")
let result_title = (result && result.length > 0)
? __("{0} bank transaction(s) created", [result.length])
: __("This bank account is already synchronized");

let result_msg = `
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
<h5 class="text-muted">${result_title}</h5>
</div>`
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
<h5 class="text-muted">${result_title}</h5>
</div>`

this.parent.$main_section.append(result_msg)
frappe.show_alert({message:__("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator:'green'});
frappe.show_alert({ message: __("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator: 'green' });
})
})
}
Expand Down Expand Up @@ -384,7 +388,7 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
})

frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
{bank_transaction: data, freeze:true, freeze_message:__("Finding linked payments")}
{ bank_transaction: data, freeze: true, freeze_message: __("Finding linked payments") }
).then((result) => {
me.make_dialog(result)
})
Expand Down
Expand Up @@ -2,81 +2,84 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
from frappe import _
from frappe.utils.password import get_decrypted_password
from plaid import Client
from plaid.errors import APIError, ItemError
import plaid
import requests
from plaid.errors import APIError, ItemError, InvalidRequestError

import frappe
import requests
from frappe import _


class PlaidConnector():
def __init__(self, access_token=None):

plaid_settings = frappe.get_single("Plaid Settings")

self.config = {
"plaid_client_id": plaid_settings.plaid_client_id,
"plaid_secret": get_decrypted_password("Plaid Settings", "Plaid Settings", 'plaid_secret'),
"plaid_public_key": plaid_settings.plaid_public_key,
"plaid_env": plaid_settings.plaid_env
}

self.client = Client(client_id=self.config.get("plaid_client_id"),
secret=self.config.get("plaid_secret"),
public_key=self.config.get("plaid_public_key"),
environment=self.config.get("plaid_env")
)

self.access_token = access_token
self.settings = frappe.get_single("Plaid Settings")
self.products = ["auth", "transactions"]
self.client_name = frappe.local.site
self.client = plaid.Client(
client_id=self.settings.plaid_client_id,
secret=self.settings.get_password("plaid_secret"),
environment=self.settings.plaid_env,
api_version="2019-05-29"
)

def get_access_token(self, public_token):
if public_token is None:
frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error"))

response = self.client.Item.public_token.exchange(public_token)
access_token = response['access_token']

access_token = response["access_token"]
return access_token

def get_link_token(self):
token_request = {
"client_name": self.client_name,
"client_id": self.settings.plaid_client_id,
"secret": self.settings.plaid_secret,
"products": self.products,
# only allow Plaid-supported languages and countries (LAST: Sep-19-2020)
"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
"country_codes": ["US", "CA", "FR", "IE", "NL", "ES", "GB"],
"user": {
"client_user_id": frappe.generate_hash(frappe.session.user, length=32)
}
}

try:
response = self.client.LinkToken.create(token_request)
except InvalidRequestError:
frappe.log_error(frappe.get_traceback(), _("Plaid invalid request error"))
frappe.msgprint(_("Please check your Plaid client ID and secret values"))
except APIError as e:
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
frappe.throw(_(str(e)), title=_("Authentication Failed"))
else:
return response["link_token"]

def auth(self):
try:
self.client.Auth.get(self.access_token)
print("Authentication successful.....")
except ItemError as e:
if e.code == 'ITEM_LOGIN_REQUIRED':
pass
else:
if e.code == "ITEM_LOGIN_REQUIRED":
pass
except APIError as e:
if e.code == 'PLANNED_MAINTENANCE':
pass
else:
if e.code == "PLANNED_MAINTENANCE":
pass
except requests.Timeout:
pass
except Exception as e:
print(e)
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
frappe.msgprint({"title": _("Authentication Failed"), "message":e, "raise_exception":1, "indicator":'red'})
frappe.throw(_(str(e)), title=_("Authentication Failed"))

def get_transactions(self, start_date, end_date, account_id=None):
try:
self.auth()
if account_id:
account_ids = [account_id]

response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, account_ids=account_ids)
self.auth()
account_ids = list(account_id) if account_id else None

else:
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date)

transactions = response['transactions']

while len(transactions) < response['total_transactions']:
try:
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, account_ids=account_ids)
transactions = response["transactions"]
while len(transactions) < response["total_transactions"]:
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions))
transactions.extend(response['transactions'])
transactions.extend(response["transactions"])
return transactions
except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
Expand Up @@ -4,14 +4,14 @@
frappe.provide("erpnext.integrations");

frappe.ui.form.on('Plaid Settings', {
enabled: function(frm) {
enabled: function (frm) {
frm.toggle_reqd('plaid_client_id', frm.doc.enabled);
frm.toggle_reqd('plaid_secret', frm.doc.enabled);
frm.toggle_reqd('plaid_public_key', frm.doc.enabled);
frm.toggle_reqd('plaid_env', frm.doc.enabled);
},
refresh: function(frm) {
if(frm.doc.enabled) {

refresh: function (frm) {
if (frm.doc.enabled) {
frm.add_custom_button('Link a new bank account', () => {
new erpnext.integrations.plaidLink(frm);
});
Expand All @@ -22,17 +22,16 @@ frappe.ui.form.on('Plaid Settings', {
erpnext.integrations.plaidLink = class plaidLink {
constructor(parent) {
this.frm = parent;
this.product = ["transactions", "auth"];
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
this.init_config();
}

init_config() {
const me = this;
me.plaid_env = me.frm.doc.plaid_env;
me.plaid_public_key = me.frm.doc.plaid_public_key;
me.client_name = frappe.boot.sitename;
me.init_plaid();
async init_config() {
this.product = ["auth", "transactions"];
this.plaid_env = this.frm.doc.plaid_env;
this.client_name = frappe.boot.sitename;
this.token = await this.frm.call("get_link_token").then(resp => resp.message);
this.init_plaid();
}

init_plaid() {
Expand Down Expand Up @@ -69,39 +68,43 @@ erpnext.integrations.plaidLink = class plaidLink {
}

onScriptLoaded(me) {
me.linkHandler = window.Plaid.create({
me.linkHandler = Plaid.create({
clientName: me.client_name,
product: me.product,
env: me.plaid_env,
key: me.plaid_public_key,
onSuccess: me.plaid_success,
product: me.product
token: me.token,
onSuccess: me.plaid_success
});
}

onScriptError(error) {
frappe.msgprint('There was an issue loading the link-initialize.js script');
frappe.msgprint("There was an issue connecting to Plaid's authentication server");
frappe.msgprint(error);
}

plaid_success(token, response) {
const me = this;

frappe.prompt({
fieldtype:"Link",
fieldtype: "Link",
options: "Company",
label:__("Company"),
fieldname:"company",
reqd:1
label: __("Company"),
fieldname: "company",
reqd: 1
}, (data) => {
me.company = data.company;
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response})
.then((result) => {
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response,
bank: result, company: me.company});
})
.then(() => {
frappe.show_alert({message:__("Bank accounts added"), indicator:'green'});
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {
token: token,
response: response
}).then((result) => {
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {
response: response,
bank: result,
company: me.company
});
}).then(() => {
frappe.show_alert({ message: __("Bank accounts added"), indicator: 'green' });
});
}, __("Select a company"), __("Continue"));
}
};
@@ -1,5 +1,4 @@
{
"actions": [],
"creation": "2018-10-25 10:02:48.656165",
"doctype": "DocType",
"editable_grid": 1,
Expand All @@ -12,7 +11,6 @@
"plaid_client_id",
"plaid_secret",
"column_break_7",
"plaid_public_key",
"plaid_env"
],
"fields": [
Expand Down Expand Up @@ -41,12 +39,6 @@
"in_list_view": 1,
"label": "Plaid Secret"
},
{
"fieldname": "plaid_public_key",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Plaid Public Key"
},
{
"fieldname": "plaid_env",
"fieldtype": "Select",
Expand All @@ -69,8 +61,7 @@
}
],
"issingle": 1,
"links": [],
"modified": "2020-02-07 15:21:11.616231",
"modified": "2020-09-12 02:31:44.542385",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Plaid Settings",
Expand Down