plaid-integration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Plaid API Integration

Plaid API 集成

Integrate Plaid for connecting bank accounts and syncing transactions in TypeScript applications using Bun.
在使用Bun的TypeScript应用中集成Plaid,实现银行账户连接与交易同步。

Quick Start

快速开始

bash
bun add plaid
typescript
import { Configuration, PlaidApi, PlaidEnvironments } from 'plaid';

const plaidClient = new PlaidApi(new Configuration({
  basePath: PlaidEnvironments[process.env.PLAID_ENV || 'sandbox'],
  baseOptions: {
    headers: {
      'PLAID-CLIENT-ID': process.env.PLAID_CLIENT_ID,
      'PLAID-SECRET': process.env.PLAID_SECRET,
    }
  }
}));
bash
bun add plaid
typescript
import { Configuration, PlaidApi, PlaidEnvironments } from 'plaid';

const plaidClient = new PlaidApi(new Configuration({
  basePath: PlaidEnvironments[process.env.PLAID_ENV || 'sandbox'],
  baseOptions: {
    headers: {
      'PLAID-CLIENT-ID': process.env.PLAID_CLIENT_ID,
      'PLAID-SECRET': process.env.PLAID_SECRET,
    }
  }
}));

Environment Setup

环境配置

EnvironmentUse CaseHTTPS RequiredReal Data
sandbox
Development/testingNoNo (test accounts)
development
Limited ProductionYes for redirect, No for popupYes (with limits)
production
Full productionYesYes
Critical: Use popup mode (no
redirect_uri
) for local development to avoid HTTPS requirements:
typescript
// Popup mode - works with HTTP localhost
const linkConfig = {
  user: { client_user_id: `user-${Date.now()}` },
  client_name: 'My App',
  products: ['transactions'],
  country_codes: ['US'],
  language: 'en',
  // NO redirect_uri = popup mode
};
环境使用场景是否需要HTTPS是否使用真实数据
sandbox
开发/测试否(使用测试账户)
development
有限生产环境重定向需HTTPS,弹窗模式无需是(有使用限制)
production
正式生产环境
重要提示:本地开发时使用弹窗模式(不设置
redirect_uri
),可规避HTTPS要求:
typescript
// 弹窗模式 - 支持HTTP localhost
const linkConfig = {
  user: { client_user_id: `user-${Date.now()}` },
  client_name: 'My App',
  products: ['transactions'],
  country_codes: ['US'],
  language: 'en',
  // 不设置redirect_uri = 弹窗模式
};

Authentication Flow

认证流程

The Plaid Link flow has 3 steps:
  1. Create Link Token (backend) → Returns temporary token for Link UI
  2. User Authenticates (frontend) → Opens Plaid Link, user logs into bank
  3. Exchange Tokens (backend) → Trade public_token for permanent access_token
See
references/code-examples.md
for complete implementation.
Plaid Link流程分为3个步骤:
  1. 创建Link Token(后端)→ 返回用于初始化Link UI的临时令牌
  2. 用户认证(前端)→ 打开Plaid Link,用户登录银行账户
  3. 令牌交换(后端)→ 将public_token兑换为永久的access_token
完整实现可参考
references/code-examples.md

Key Concepts

核心概念

  • Item: A bank connection (one per institution per user)
  • Access Token: Permanent credential for API calls (store securely)
  • Public Token: Temporary token from Link (exchange immediately)
  • Link Token: Short-lived token to initialize Link UI
  • Item:一个银行账户连接(每个用户对应每家机构一个Item)
  • Access Token:用于API调用的永久凭证(需安全存储)
  • Public Token:来自Link的临时令牌(需立即兑换)
  • Link Token:用于初始化Link UI的短期令牌

Products

产品功能

Common products to request:
ProductDescription
transactions
Transaction history and real-time updates
auth
Account and routing numbers
identity
Account holder information
investments
Investment account data
liabilities
Loan and credit card data
常用的请求产品:
产品描述
transactions
交易历史与实时更新
auth
账户与路由号码
identity
账户持有人信息
investments
投资账户数据
liabilities
贷款与信用卡数据

Database Schema

数据库 Schema

For multi-account support, use SQLite with Bun's built-in driver:
typescript
import { Database } from "bun:sqlite";

db.run(`
  CREATE TABLE IF NOT EXISTS items (
    id TEXT PRIMARY KEY,
    access_token TEXT NOT NULL,
    institution_id TEXT,
    institution_name TEXT,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
  )
`);

db.run(`
  CREATE TABLE IF NOT EXISTS accounts (
    id TEXT PRIMARY KEY,
    item_id TEXT NOT NULL,
    name TEXT NOT NULL,
    type TEXT NOT NULL,
    subtype TEXT,
    current_balance REAL,
    FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE
  )
`);

db.run(`
  CREATE TABLE IF NOT EXISTS transactions (
    id TEXT PRIMARY KEY,
    account_id TEXT NOT NULL,
    amount REAL NOT NULL,
    date TEXT NOT NULL,
    name TEXT NOT NULL,
    merchant_name TEXT,
    category TEXT,
    FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE
  )
`);
如需支持多账户,可使用Bun内置驱动的SQLite:
typescript
import { Database } from "bun:sqlite";

db.run(`
  CREATE TABLE IF NOT EXISTS items (
    id TEXT PRIMARY KEY,
    access_token TEXT NOT NULL,
    institution_id TEXT,
    institution_name TEXT,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
  )
`);

db.run(`
  CREATE TABLE IF NOT EXISTS accounts (
    id TEXT PRIMARY KEY,
    item_id TEXT NOT NULL,
    name TEXT NOT NULL,
    type TEXT NOT NULL,
    subtype TEXT,
    current_balance REAL,
    FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE
  )
`);

db.run(`
  CREATE TABLE IF NOT EXISTS transactions (
    id TEXT PRIMARY KEY,
    account_id TEXT NOT NULL,
    amount REAL NOT NULL,
    date TEXT NOT NULL,
    name TEXT NOT NULL,
    merchant_name TEXT,
    category TEXT,
    FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE
  )
`);

Transaction Pagination

交易分页

Plaid returns max 500 transactions per request. Always paginate:
typescript
let offset = 0;
const count = 500;
let hasMore = true;

while (hasMore) {
  const response = await plaidClient.transactionsGet({
    access_token,
    start_date: '2023-01-01',
    end_date: '2024-12-31',
    options: { count, offset },
  });

  // Process response.data.transactions
  offset += response.data.transactions.length;
  hasMore = offset < response.data.total_transactions;
}
Plaid每次请求最多返回500条交易记录,需始终使用分页:
typescript
let offset = 0;
const count = 500;
let hasMore = true;

while (hasMore) {
  const response = await plaidClient.transactionsGet({
    access_token,
    start_date: '2023-01-01',
    end_date: '2024-12-31',
    options: { count, offset },
  });

  // 处理response.data.transactions
  offset += response.data.transactions.length;
  hasMore = offset < response.data.total_transactions;
}

Common Errors

常见错误

Error CodeCauseSolution
INVALID_ACCESS_TOKEN
Token expired or invalidRe-link the account
ITEM_LOGIN_REQUIRED
Bank requires re-authenticationUse update mode Link
INVALID_FIELD
+ "redirect_uri must use HTTPS"
Using redirect in dev/prodUse popup mode or HTTPS
PRODUCTS_NOT_SUPPORTED
Institution doesn't support productCheck institution capabilities
错误代码原因解决方案
INVALID_ACCESS_TOKEN
令牌过期或无效重新关联账户
ITEM_LOGIN_REQUIRED
银行要求重新认证使用更新模式的Link
INVALID_FIELD
+ "redirect_uri must use HTTPS"
在开发/生产环境中使用了重定向模式使用弹窗模式或配置HTTPS
PRODUCTS_NOT_SUPPORTED
机构不支持该产品功能检查机构的功能支持情况

Documentation Links

文档链接

Reference Files

参考文件

  • references/code-examples.md
    - Complete implementation patterns
  • references/api-reference.md
    - API endpoints and responses
  • references/code-examples.md
    - 完整实现示例
  • references/api-reference.md
    - API端点与响应参考