plaid
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePlaid Banking API Expert
Plaid银行API专家
Plaid connects applications to users' bank accounts for financial data access, payments, and identity verification.
Plaid可将应用与用户的银行账户连接,实现金融数据访问、支付和身份验证功能。
When to Use
适用场景
- Connecting bank accounts in fintech apps
- Implementing Plaid Link flow
- Retrieving transactions, balances, or account info
- Setting up ACH transfers
- Identity/income verification
- Handling Plaid webhooks
- 在金融科技应用中连接银行账户
- 实现Plaid Link流程
- 获取交易记录、账户余额或账户信息
- 设置ACH转账
- 身份/收入验证
- 处理Plaid webhooks
Core Products
核心产品
| Product | Purpose |
|---|---|
| Auth | Bank account/routing numbers for ACH |
| Transactions | Transaction history (up to 24 months) |
| Identity | Verify user via bank account ownership |
| Balance | Real-time account balances |
| Investments | Holdings from investment accounts |
| Liabilities | Loan and credit card data |
| 产品 | 用途 |
|---|---|
| Auth | 获取用于ACH的银行账户/路由号码 |
| Transactions | 获取交易记录(最长24个月) |
| Identity | 通过银行账户所有权验证用户身份 |
| Balance | 查询实时账户余额 |
| Investments | 获取投资账户的持仓信息 |
| Liabilities | 获取贷款和信用卡数据 |
Quick Start
快速开始
1. Install SDK
1. 安装SDK
bash
npm install plaid react-plaid-linkbash
npm install plaid react-plaid-link2. Create Plaid Client
2. 创建Plaid客户端
typescript
import { Configuration, PlaidApi, PlaidEnvironments } from "plaid";
const client = new PlaidApi(
new Configuration({
basePath: PlaidEnvironments.sandbox,
baseOptions: {
headers: {
"PLAID-CLIENT-ID": process.env.PLAID_CLIENT_ID,
"PLAID-SECRET": process.env.PLAID_SECRET,
},
},
})
);typescript
import { Configuration, PlaidApi, PlaidEnvironments } from "plaid";
const client = new PlaidApi(
new Configuration({
basePath: PlaidEnvironments.sandbox,
baseOptions: {
headers: {
"PLAID-CLIENT-ID": process.env.PLAID_CLIENT_ID,
"PLAID-SECRET": process.env.PLAID_SECRET,
},
},
})
);3. Create Link Token (Server)
3. 创建Link Token(服务端)
typescript
// POST /api/plaid/create-link-token
export async function POST(req: Request) {
const response = await client.linkTokenCreate({
user: { client_user_id: userId },
client_name: "Your App",
products: ["auth", "transactions"],
country_codes: ["US"],
language: "en",
});
return Response.json({ link_token: response.data.link_token });
}typescript
// POST /api/plaid/create-link-token
export async function POST(req: Request) {
const response = await client.linkTokenCreate({
user: { client_user_id: userId },
client_name: "Your App",
products: ["auth", "transactions"],
country_codes: ["US"],
language: "en",
});
return Response.json({ link_token: response.data.link_token });
}4. Plaid Link (Client)
4. 集成Plaid Link(客户端)
tsx
import { usePlaidLink } from "react-plaid-link";
function ConnectBank({ linkToken }) {
const { open, ready } = usePlaidLink({
token: linkToken,
onSuccess: async (public_token, metadata) => {
// Exchange for access_token on server
await fetch("/api/plaid/exchange-token", {
method: "POST",
body: JSON.stringify({ public_token }),
});
},
});
return (
<button onClick={() => open()} disabled={!ready}>
Connect Bank Account
</button>
);
}tsx
import { usePlaidLink } from "react-plaid-link";
function ConnectBank({ linkToken }) {
const { open, ready } = usePlaidLink({
token: linkToken,
onSuccess: async (public_token, metadata) => {
// 在服务端兑换为access_token
await fetch("/api/plaid/exchange-token", {
method: "POST",
body: JSON.stringify({ public_token }),
});
},
});
return (
<button onClick={() => open()} disabled={!ready}>
连接银行账户
</button>
);
}5. Exchange Token (Server)
5. 兑换Token(服务端)
typescript
// POST /api/plaid/exchange-token
export async function POST(req: Request) {
const { public_token } = await req.json();
const response = await client.itemPublicTokenExchange({
public_token,
});
// Store access_token securely (encrypted in database)
await db.users.update(userId, {
plaid_access_token: response.data.access_token,
plaid_item_id: response.data.item_id,
});
return Response.json({ success: true });
}typescript
// POST /api/plaid/exchange-token
export async function POST(req: Request) {
const { public_token } = await req.json();
const response = await client.itemPublicTokenExchange({
public_token,
});
// 安全存储access_token(加密后存入数据库)
await db.users.update(userId, {
plaid_access_token: response.data.access_token,
plaid_item_id: response.data.item_id,
});
return Response.json({ success: true });
}Data Retrieval
数据获取
Get Auth (Account/Routing Numbers)
获取Auth信息(账户/路由号码)
typescript
const response = await client.authGet({ access_token });
const ach = response.data.numbers.ach[0];
console.log("Account:", ach.account);
console.log("Routing:", ach.routing);typescript
const response = await client.authGet({ access_token });
const ach = response.data.numbers.ach[0];
console.log("Account:", ach.account);
console.log("Routing:", ach.routing);Get Transactions
获取交易记录
typescript
const response = await client.transactionsGet({
access_token,
start_date: "2024-01-01",
end_date: "2024-12-31",
});
let transactions = response.data.transactions;
// Handle pagination
while (transactions.length < response.data.total_transactions) {
const more = await client.transactionsGet({
access_token,
start_date: "2024-01-01",
end_date: "2024-12-31",
offset: transactions.length,
});
transactions = transactions.concat(more.data.transactions);
}Transaction object:
typescript
{
transaction_id: "abc123",
amount: 12.34, // Positive = outflow
date: "2024-11-16",
name: "Starbucks",
merchant_name: "Starbucks",
category: ["Food and Drink", "Coffee Shop"],
pending: false,
}typescript
const response = await client.transactionsGet({
access_token,
start_date: "2024-01-01",
end_date: "2024-12-31",
});
let transactions = response.data.transactions;
// 处理分页
while (transactions.length < response.data.total_transactions) {
const more = await client.transactionsGet({
access_token,
start_date: "2024-01-01",
end_date: "2024-12-31",
offset: transactions.length,
});
transactions = transactions.concat(more.data.transactions);
}交易对象结构:
typescript
{
transaction_id: "abc123",
amount: 12.34, // 正数表示支出
date: "2024-11-16",
name: "Starbucks",
merchant_name: "Starbucks",
category: ["Food and Drink", "Coffee Shop"],
pending: false,
}Get Balance
获取账户余额
typescript
const response = await client.accountsBalanceGet({ access_token });
response.data.accounts.forEach((account) => {
console.log(`${account.name}: $${account.balances.current}`);
});typescript
const response = await client.accountsBalanceGet({ access_token });
response.data.accounts.forEach((account) => {
console.log(`${account.name}: $${account.balances.current}`);
});Get Identity
获取身份信息
typescript
const response = await client.identityGet({ access_token });
const owner = response.data.accounts[0].owners[0];
console.log("Name:", owner.names[0]);
console.log("Email:", owner.emails[0].data);
console.log("Phone:", owner.phone_numbers[0].data);typescript
const response = await client.identityGet({ access_token });
const owner = response.data.accounts[0].owners[0];
console.log("Name:", owner.names[0]);
console.log("Email:", owner.emails[0].data);
console.log("Phone:", owner.phone_numbers[0].data);Webhooks
Webhooks
Setup Endpoint
设置端点
typescript
// POST /api/plaid/webhook
export async function POST(req: Request) {
const { webhook_type, webhook_code, item_id } = await req.json();
switch (webhook_type) {
case "TRANSACTIONS":
if (webhook_code === "DEFAULT_UPDATE") {
// New transactions available - fetch them
await syncTransactions(item_id);
}
break;
case "ITEM":
if (webhook_code === "ERROR") {
// Connection issue - prompt user to re-authenticate
await notifyUserReauth(item_id);
}
break;
}
return Response.json({ received: true });
}Key webhook events:
| Event | Meaning |
|---|---|
| First batch ready |
| New transactions |
| Connection issue |
| Credentials expiring |
typescript
// POST /api/plaid/webhook
export async function POST(req: Request) {
const { webhook_type, webhook_code, item_id } = await req.json();
switch (webhook_type) {
case "TRANSACTIONS":
if (webhook_code === "DEFAULT_UPDATE") {
// 有新交易记录可用 - 获取数据
await syncTransactions(item_id);
}
break;
case "ITEM":
if (webhook_code === "ERROR") {
// 连接出现问题 - 提示用户重新认证
await notifyUserReauth(item_id);
}
break;
}
return Response.json({ received: true });
}关键Webhook事件:
| 事件 | 含义 |
|---|---|
| 第一批交易数据已就绪 |
| 有新的交易记录 |
| 连接出现问题 |
| 凭证即将过期 |
Environments
运行环境
| Environment | Use Case | Base Path |
|---|---|---|
| Sandbox | Testing | |
| Development | Limited live (100 connections) | |
| Production | Live | |
| 环境 | 适用场景 | 基础路径 |
|---|---|---|
| Sandbox | 测试环境 | |
| Development | 有限的真实环境(最多100个连接) | |
| Production | 生产环境 | |
Sandbox Test Credentials
Sandbox测试凭证
- Username:
user_good - Password:
pass_good - MFA:
1234
- 用户名:
user_good - 密码:
pass_good - 多因素认证码:
1234
Error Handling
错误处理
Re-authentication (Update Mode)
重新认证(更新模式)
When credentials expire:
typescript
// Create link token for update mode
const response = await client.linkTokenCreate({
user: { client_user_id: userId },
client_name: "Your App",
access_token: existingAccessToken, // Triggers update mode
country_codes: ["US"],
language: "en",
});当凭证过期时:
typescript
// 创建用于更新模式的link token
const response = await client.linkTokenCreate({
user: { client_user_id: userId },
client_name: "Your App",
access_token: existingAccessToken, // 触发更新模式
country_codes: ["US"],
language: "en",
});Common Errors
常见错误
| Error | Solution |
|---|---|
| Re-authenticate via Link update mode |
| Implement exponential backoff |
| Wait for webhook or retry |
| 错误 | 解决方案 |
|---|---|
| 通过Link更新模式重新认证 |
| 实现指数退避机制 |
| 等待Webhook通知或重试 |
Security Best Practices
安全最佳实践
DO:
- Store access tokens encrypted in database
- Use environment variables for credentials
- Verify webhook signatures
- Use HTTPS for all endpoints
DON'T:
- Expose secret keys client-side
- Log access tokens
- Store credentials in code
建议:
- 将access token加密后存储在数据库中
- 使用环境变量存储凭证
- 验证Webhook签名
- 所有端点使用HTTPS
禁止:
- 在客户端暴露密钥
- 记录access token
- 在代码中硬编码凭证
Next.js App Router Example
Next.js App Router示例
typescript
// app/api/plaid/create-link-token/route.ts
import { NextResponse } from "next/server";
import { Configuration, PlaidApi, PlaidEnvironments } from "plaid";
const client = new PlaidApi(
new Configuration({
basePath: PlaidEnvironments.sandbox,
baseOptions: {
headers: {
"PLAID-CLIENT-ID": process.env.PLAID_CLIENT_ID!,
"PLAID-SECRET": process.env.PLAID_SECRET!,
},
},
})
);
export async function POST(req: Request) {
const session = await getSession();
const response = await client.linkTokenCreate({
user: { client_user_id: session.user.id },
client_name: "Your App",
products: ["auth", "transactions"],
country_codes: ["US"],
language: "en",
webhook: `${process.env.NEXT_PUBLIC_URL}/api/plaid/webhook`,
});
return NextResponse.json({ link_token: response.data.link_token });
}typescript
// app/api/plaid/create-link-token/route.ts
import { NextResponse } from "next/server";
import { Configuration, PlaidApi, PlaidEnvironments } from "plaid";
const client = new PlaidApi(
new Configuration({
basePath: PlaidEnvironments.sandbox,
baseOptions: {
headers: {
"PLAID-CLIENT-ID": process.env.PLAID_CLIENT_ID!,
"PLAID-SECRET": process.env.PLAID_SECRET!,
},
},
})
);
export async function POST(req: Request) {
const session = await getSession();
const response = await client.linkTokenCreate({
user: { client_user_id: session.user.id },
client_name: "Your App",
products: ["auth", "transactions"],
country_codes: ["US"],
language: "en",
webhook: `${process.env.NEXT_PUBLIC_URL}/api/plaid/webhook`,
});
return NextResponse.json({ link_token: response.data.link_token });
}Implementation Checklist
实施检查清单
- Sign up for Plaid account
- Get client ID and secret
- Install and
plaidreact-plaid-link - Set environment variables
- Create link token endpoint
- Implement token exchange endpoint
- Integrate Plaid Link on frontend
- Store access tokens securely
- Set up webhook endpoint
- Handle re-authentication errors
- Test with sandbox credentials
- 注册Plaid账户
- 获取客户端ID和密钥
- 安装和
plaidreact-plaid-link - 设置环境变量
- 创建Link Token端点
- 实现Token兑换端点
- 在前端集成Plaid Link
- 安全存储access token
- 设置Webhook端点
- 处理重新认证错误
- 使用Sandbox测试凭证进行测试
Resources
相关资源
- Docs: https://plaid.com/docs/
- API Reference: https://plaid.com/docs/api/
- Quickstart: https://github.com/plaid/quickstart
- 官方文档: https://plaid.com/docs/
- API参考: https://plaid.com/docs/api/
- 快速开始项目: https://github.com/plaid/quickstart