UNPKG

n8n-nodes-plaid

Version:

n8n community node for Plaid financial data integration - the definitive financial node for n8n

171 lines (170 loc) 6.38 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PlaidHelpers = void 0; exports.createLinkToken = createLinkToken; exports.exchangePublicToken = exchangePublicToken; exports.getAccessToken = getAccessToken; exports.requiresAccessToken = requiresAccessToken; const plaid_1 = require("plaid"); class PlaidHelpers { /** * Get Plaid environment URL */ static getEnvironmentUrl(environment) { return environment === 'production' ? plaid_1.PlaidEnvironments.production : plaid_1.PlaidEnvironments.sandbox; } /** * Format transaction amount (Plaid returns negative for outflow) */ static formatTransactionAmount(amount) { return { amount: Math.abs(amount), type: amount < 0 ? 'expense' : 'income', }; } /** * Parse account IDs from comma-separated string */ static parseAccountIds(accountIds) { if (!accountIds.trim()) return undefined; return accountIds.split(',').map(id => id.trim()).filter(Boolean); } /** * Format date for Plaid API (YYYY-MM-DD) */ static formatDate(dateString) { return dateString.split('T')[0]; } /** * Enhanced categorization mapping */ static enhanceCategories(transaction) { const categories = transaction.category || []; const personalFinanceCategory = transaction.personal_finance_category; return { ...transaction, category_primary: categories[0] || 'Other', category_secondary: categories[1] || '', category_detailed: categories[2] || '', category_full: categories.join(' > ') || 'Other', enhanced_category: personalFinanceCategory?.primary || categories[0] || 'Other', enhanced_subcategory: personalFinanceCategory?.detailed || categories[1] || '', }; } /** * Detect recurring transactions */ static detectRecurring(description) { const recurringKeywords = [ 'netflix', 'spotify', 'subscription', 'monthly', 'annual', 'insurance', 'mortgage', 'rent', 'gym', 'membership', 'utilities', 'phone', 'internet', 'recurring' ]; const lowerDesc = description.toLowerCase(); return recurringKeywords.some(keyword => lowerDesc.includes(keyword)); } /** * Calculate spending score based on amount and category */ static calculateSpendingScore(amount, categories) { const absAmount = Math.abs(amount); let score = Math.min(absAmount / 100, 10); // Base score 0-10 // Adjust based on category const categoryMultipliers = { 'Food and Drink': 1.0, 'Shops': 1.2, 'Recreation': 1.3, 'Transportation': 0.8, 'Healthcare': 0.7, 'Bills': 0.5, 'Transfer': 0.3, }; const primaryCategory = categories?.[0]; const multiplier = categoryMultipliers[primaryCategory] || 1.0; return Math.round(score * multiplier * 10) / 10; } } exports.PlaidHelpers = PlaidHelpers; /** * Create a Link token for Plaid Link initialization */ async function createLinkToken(client, credentials, userId = 'default_user', products = ['transactions', 'auth']) { const countryCodes = credentials.countryCodes ? credentials.countryCodes.split(',').map((code) => code.trim()) : ['US']; // Convert string products to Products enum const plaidProducts = products.map(product => { switch (product.toLowerCase()) { case 'transactions': return plaid_1.Products.Transactions; case 'auth': return plaid_1.Products.Auth; case 'identity': return plaid_1.Products.Identity; case 'assets': return plaid_1.Products.Assets; case 'investments': return plaid_1.Products.Investments; case 'liabilities': return plaid_1.Products.Liabilities; default: return plaid_1.Products.Transactions; } }); const request = { client_name: credentials.clientName || 'n8n Plaid Integration', country_codes: countryCodes, language: credentials.language || 'en', user: { client_user_id: userId, }, products: plaidProducts, }; const response = await client.linkTokenCreate(request); return response.data.link_token; } /** * Exchange a public token for an access token */ async function exchangePublicToken(client, publicToken) { const request = { public_token: publicToken, }; const response = await client.itemPublicTokenExchange(request); return { accessToken: response.data.access_token, itemId: response.data.item_id, }; } /** * Get the appropriate access token based on authentication method */ async function getAccessToken(client, credentials) { const authMethod = credentials.authMethod || 'accessToken'; switch (authMethod) { case 'accessToken': if (!credentials.accessToken) { throw new Error('Access token is required for legacy authentication method'); } return credentials.accessToken; case 'publicToken': if (!credentials.publicToken) { throw new Error('Public token is required for public token exchange method'); } const { accessToken } = await exchangePublicToken(client, credentials.publicToken); return accessToken; case 'clientOnly': throw new Error('Access token not available for client-only authentication method'); default: throw new Error(`Unknown authentication method: ${authMethod}`); } } /** * Check if operation requires access token */ function requiresAccessToken(resource, operation) { // Operations that don't require access token (only client credentials) const noTokenOperations = [ { resource: 'link', operation: 'createToken' }, { resource: 'link', operation: 'exchangeToken' }, { resource: 'institution', operation: 'search' }, { resource: 'institution', operation: 'getById' }, ]; return !noTokenOperations.some(op => op.resource === resource && op.operation === operation); }