n8n-nodes-plaid
Version:
The definitive Plaid financial integration node for n8n - Zero Dependencies for Cloud Verification
632 lines (631 loc) • 26 kB
JavaScript
"use strict";
// ==============================================================================
// PLAID NODE - NO DEPENDENCIES VERSION
// This version removes the Plaid SDK dependency for n8n Cloud verification
// ==============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.Plaid = void 0;
const n8n_workflow_1 = require("n8n-workflow");
// ==============================================================================
// PLAID API HELPER CLASS
// ==============================================================================
class PlaidApiHelper {
constructor(credentials, httpRequest) {
this.credentials = credentials;
this.httpRequest = httpRequest;
}
getBaseUrl() {
switch (this.credentials.environment) {
case 'sandbox':
return 'https://sandbox.plaid.com';
case 'development':
return 'https://development.plaid.com';
case 'production':
return 'https://production.plaid.com';
default:
return 'https://sandbox.plaid.com';
}
}
async makeRequest(endpoint, body) {
const options = {
method: 'POST',
url: `${this.getBaseUrl()}${endpoint}`,
headers: {
'Content-Type': 'application/json',
'PLAID-CLIENT-ID': this.credentials.clientId,
'PLAID-SECRET': this.credentials.secret,
'Plaid-Version': '2020-09-14',
},
body: {
...body,
client_id: this.credentials.clientId,
secret: this.credentials.secret,
},
json: true,
};
try {
const response = await this.httpRequest(options);
return response;
}
catch (error) {
// Enhanced error handling
if (error.response?.body?.error_code) {
const plaidError = error.response.body;
throw new Error(`Plaid API Error (${plaidError.error_code}): ${plaidError.error_message}`);
}
throw new Error(`Plaid API Request Failed: ${error.message}`);
}
}
// ==============================================================================
// TRANSACTION OPERATIONS
// ==============================================================================
async getTransactions(accessToken, startDate, endDate, options = {}) {
return this.makeRequest('/transactions/get', {
access_token: accessToken,
start_date: startDate,
end_date: endDate,
count: options.count || 500,
offset: options.offset || 0,
});
}
async syncTransactions(accessToken, cursor) {
const body = {
access_token: accessToken,
};
if (cursor) {
body.cursor = cursor;
}
return this.makeRequest('/transactions/sync', body);
}
// ==============================================================================
// ACCOUNT OPERATIONS
// ==============================================================================
async getAccounts(accessToken) {
return this.makeRequest('/accounts/get', {
access_token: accessToken,
});
}
async getAccountBalances(accessToken) {
return this.makeRequest('/accounts/balance/get', {
access_token: accessToken,
});
}
// ==============================================================================
// AUTH OPERATIONS
// ==============================================================================
async getAuth(accessToken) {
return this.makeRequest('/auth/get', {
access_token: accessToken,
});
}
// ==============================================================================
// INSTITUTION OPERATIONS
// ==============================================================================
async searchInstitutions(query, countryCodes = ['US']) {
return this.makeRequest('/institutions/search', {
query,
products: ['transactions'],
country_codes: countryCodes,
});
}
async getInstitutionById(institutionId, countryCodes = ['US']) {
return this.makeRequest('/institutions/get_by_id', {
institution_id: institutionId,
country_codes: countryCodes,
});
}
// ==============================================================================
// ITEM OPERATIONS
// ==============================================================================
async getItem(accessToken) {
return this.makeRequest('/item/get', {
access_token: accessToken,
});
}
async removeItem(accessToken) {
return this.makeRequest('/item/remove', {
access_token: accessToken,
});
}
// ==============================================================================
// IDENTITY OPERATIONS
// ==============================================================================
async getIdentity(accessToken) {
return this.makeRequest('/identity/get', {
access_token: accessToken,
});
}
}
// ==============================================================================
// MAIN NODE IMPLEMENTATION
// ==============================================================================
class Plaid {
constructor() {
this.description = {
displayName: 'Plaid',
name: 'plaid',
icon: 'file:plaid.svg',
group: ['input'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Access the Plaid API - No Dependencies',
defaults: {
name: 'Plaid',
},
inputs: ["main" /* NodeConnectionType.Main */],
outputs: ["main" /* NodeConnectionType.Main */],
credentials: [
{
name: 'plaidApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Transaction',
value: 'transaction',
},
{
name: 'Account',
value: 'account',
},
{
name: 'Auth',
value: 'auth',
},
{
name: 'Institution',
value: 'institution',
},
{
name: 'Item',
value: 'item',
},
{
name: 'Identity',
value: 'identity',
},
],
default: 'transaction',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['transaction'],
},
},
options: [
{
name: 'Get Range',
value: 'getRange',
description: 'Get transactions within a date range',
action: 'Get transactions within a date range',
},
{
name: 'Sync',
value: 'sync',
description: 'Get new and updated transactions',
action: 'Get new and updated transactions',
},
],
default: 'sync',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['account'],
},
},
options: [
{
name: 'Get All',
value: 'getAll',
description: 'Get all connected accounts',
action: 'Get all connected accounts',
},
{
name: 'Get Balances',
value: 'getBalances',
description: 'Get real-time account balances',
action: 'Get real-time account balances',
},
],
default: 'getAll',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['auth'],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get bank account and routing numbers',
action: 'Get bank account and routing numbers',
},
],
default: 'get',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['institution'],
},
},
options: [
{
name: 'Search',
value: 'search',
description: 'Search for institutions',
action: 'Search for institutions',
},
{
name: 'Get by ID',
value: 'getById',
description: 'Get institution by ID',
action: 'Get institution by ID',
},
],
default: 'search',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['item'],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get item information',
action: 'Get item information',
},
{
name: 'Remove',
value: 'remove',
description: 'Remove item connection',
action: 'Remove item connection',
},
],
default: 'get',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['identity'],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get account owner information',
action: 'Get account owner information',
},
],
default: 'get',
},
// Access Token field
{
displayName: 'Access Token',
name: 'accessToken',
type: 'string',
required: true,
default: '',
description: 'The Plaid access token for the user account',
},
// Date fields for transaction range
{
displayName: 'Start Date',
name: 'startDate',
type: 'dateTime',
displayOptions: {
show: {
resource: ['transaction'],
operation: ['getRange'],
},
},
default: '',
required: true,
description: 'Start date for transaction range (YYYY-MM-DD)',
},
{
displayName: 'End Date',
name: 'endDate',
type: 'dateTime',
displayOptions: {
show: {
resource: ['transaction'],
operation: ['getRange'],
},
},
default: '',
required: true,
description: 'End date for transaction range (YYYY-MM-DD)',
},
// Cursor for sync
{
displayName: 'Cursor',
name: 'cursor',
type: 'string',
displayOptions: {
show: {
resource: ['transaction'],
operation: ['sync'],
},
},
default: '',
description: 'Cursor for incremental sync (leave empty for initial sync)',
},
// Search query for institutions
{
displayName: 'Search Query',
name: 'query',
type: 'string',
displayOptions: {
show: {
resource: ['institution'],
operation: ['search'],
},
},
default: '',
required: true,
description: 'Search query for institutions',
},
// Institution ID
{
displayName: 'Institution ID',
name: 'institutionId',
type: 'string',
displayOptions: {
show: {
resource: ['institution'],
operation: ['getById'],
},
},
default: '',
required: true,
description: 'The institution ID to retrieve',
},
// Return all or limit
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['transaction'],
operation: ['getRange'],
},
},
default: false,
description: 'Whether to return all results or only up to the limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['transaction'],
operation: ['getRange'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 500,
},
default: 100,
description: 'Max number of results to return',
},
],
};
}
// eslint-disable-next-line no-unused-vars
async execute() {
const items = this.getInputData();
const returnData = [];
// Get credentials
const credentials = await this.getCredentials('plaidApi');
// Create Plaid API helper
const plaidApi = new PlaidApiHelper(credentials, this.helpers.httpRequest);
// Get parameters
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
for (let i = 0; i < items.length; i++) {
try {
let responseData;
if (resource === 'transaction') {
const accessToken = this.getNodeParameter('accessToken', i);
if (operation === 'getRange') {
const startDate = this.getNodeParameter('startDate', i);
const endDate = this.getNodeParameter('endDate', i);
const returnAll = this.getNodeParameter('returnAll', i);
const limit = returnAll ? 500 : this.getNodeParameter('limit', i);
responseData = await plaidApi.getTransactions(accessToken, startDate, endDate, {
count: limit,
});
// Return individual transactions
for (const transaction of responseData.transactions) {
returnData.push({
json: transaction,
pairedItem: { item: i },
});
}
continue;
}
else if (operation === 'sync') {
const cursor = this.getNodeParameter('cursor', i) || undefined;
responseData = await plaidApi.syncTransactions(accessToken, cursor);
// Return added transactions
for (const transaction of responseData.added) {
returnData.push({
json: {
...transaction,
sync_status: 'added',
next_cursor: responseData.next_cursor,
has_next: responseData.has_next,
},
pairedItem: { item: i },
});
}
// Return modified transactions
for (const transaction of responseData.modified) {
returnData.push({
json: {
...transaction,
sync_status: 'modified',
next_cursor: responseData.next_cursor,
has_next: responseData.has_next,
},
pairedItem: { item: i },
});
}
// Return removed transactions
for (const transaction of responseData.removed) {
returnData.push({
json: {
...transaction,
sync_status: 'removed',
next_cursor: responseData.next_cursor,
has_next: responseData.has_next,
},
pairedItem: { item: i },
});
}
continue;
}
}
else if (resource === 'account') {
const accessToken = this.getNodeParameter('accessToken', i);
if (operation === 'getAll') {
responseData = await plaidApi.getAccounts(accessToken);
}
else if (operation === 'getBalances') {
responseData = await plaidApi.getAccountBalances(accessToken);
}
// Return individual accounts
for (const account of responseData.accounts) {
returnData.push({
json: account,
pairedItem: { item: i },
});
}
continue;
}
else if (resource === 'auth') {
const accessToken = this.getNodeParameter('accessToken', i);
responseData = await plaidApi.getAuth(accessToken);
// Return auth information for each account
for (const authData of responseData.numbers.ach) {
returnData.push({
json: authData,
pairedItem: { item: i },
});
}
continue;
}
else if (resource === 'institution') {
if (operation === 'search') {
const query = this.getNodeParameter('query', i);
responseData = await plaidApi.searchInstitutions(query);
// Return individual institutions
for (const institution of responseData.institutions) {
returnData.push({
json: institution,
pairedItem: { item: i },
});
}
continue;
}
else if (operation === 'getById') {
const institutionId = this.getNodeParameter('institutionId', i);
responseData = await plaidApi.getInstitutionById(institutionId);
}
}
else if (resource === 'item') {
const accessToken = this.getNodeParameter('accessToken', i);
if (operation === 'get') {
responseData = await plaidApi.getItem(accessToken);
}
else if (operation === 'remove') {
responseData = await plaidApi.removeItem(accessToken);
}
}
else if (resource === 'identity') {
const accessToken = this.getNodeParameter('accessToken', i);
responseData = await plaidApi.getIdentity(accessToken);
// Return individual identity records
if (responseData.identity && Array.isArray(responseData.identity)) {
for (const identity of responseData.identity) {
returnData.push({
json: identity,
pairedItem: { item: i },
});
}
}
else {
// Fallback: return the entire response if identity array is not available
returnData.push({
json: responseData,
pairedItem: { item: i },
});
}
continue;
}
// Default return for operations that don't continue above
returnData.push({
json: responseData,
pairedItem: { item: i },
});
}
catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error instanceof Error ? error.message : String(error) },
pairedItem: { item: i },
});
}
else {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), error instanceof Error ? error.message : String(error), { itemIndex: i });
}
}
}
return [returnData];
}
}
exports.Plaid = Plaid;