何时使用
为产品接入 Plaid,把第三方银行账户接进你的系统时使用,覆盖:
- 首次连接银行账户、用户引导(Link token 流程)
- 拉取并增量同步交易流水(
/transactions/sync) - ACH 转账需要的账号/路由号(Auth)+ 转账前身份核验(Identity)
- 支付/扣款前的实时余额校验
- Webhook 验签、幂等与 Item 错误态(掉线、需重新授权)恢复
不该用的边界:
- 实际收单/扣款用 Stripe 等支付处理商,Plaid 只负责账户连接与取号
- 交易分类、预算、投资组合分析等数据建模不在本条范围
- 不替代环境特定的测试、合规审计(SOC2/PCI)与专家评审;Sandbox 不能反映生产复杂度
- 已废弃的 Public Key 集成(2025-01 起停用)一律不用,统一用 Link token
步骤
- 初始化客户端:用环境变量配置
PLAID_CLIENT_ID/PLAID_SECRET/PLAID_ENV,secret 仅存服务端。 - 创建 link_token:服务端
linkTokenCreate,传client_user_id、products、country_codes、webhook;要复发交易则transactions.days_requested: 180。 - 前端拉起 Link:
usePlaidLink,onSuccess拿到public_token回传服务端。 - 换永久 access_token:
itemPublicTokenExchange,加密后落库(access_token 不过期、极敏感),随后触发首次同步。 - 增量同步交易:用游标
cursor循环调用transactionsSync处理 added/modified/removed,持久化next_cursor。 - Webhook 驱动:收到
SYNC_UPDATES_AVAILABLE等再触发同步,避免轮询。 - 错误恢复:监听
ITEM类 webhook,遇ITEM_LOGIN_REQUIRED/PENDING_DISCONNECT走 Link 更新模式(传access_token而非products)。
指令
- 创建 link_token 必带
webhook与内部client_user_id,便于回调归属。 - 同步分页时若报
TRANSACTIONS_SYNC_MUTATION_DURING_PAGINATION:把cursor置null从头重跑。 - 余额:展示用
accountsGet(缓存、免费);支付/扣款决策必须用accountsBalanceGet(实时、付费)。 - ACH 取号用
authGet拿numbers.ach的routing/account;转账前用identityMatch,legal_name.score < 70拒绝。 - Webhook 必须验签:取
plaid-verificationJWT → 按kid拉 JWKS →ES256验签 → 校验request_body_sha256与 body 哈希一致 → 校验iat在 5 分钟内。 - Webhook 处理幂等:用
type:code:item:body哈希查重,先记webhookLog再异步处理,立即200。
示例
创建并交换 Link token(服务端核心):
import { Configuration, PlaidApi, PlaidEnvironments, Products, CountryCode } 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,
}},
}));
// 1) 创建 link_token
const { data } = await plaidClient.linkTokenCreate({
user: { client_user_id: userId },
client_name: 'My Finance App',
products: [Products.Transactions],
country_codes: [CountryCode.Us],
language: 'en',
webhook: 'https://yourapp.com/api/plaid/webhooks',
transactions: { days_requested: 180 }, // 复发交易需 180 天历史
});
// 2) 用 public_token 换永久 access_token,并加密落库
const ex = await plaidClient.itemPublicTokenExchange({ public_token: publicToken });
await db.plaidItem.create({ data: {
userId, itemId: ex.data.item_id,
accessToken: await encrypt(ex.data.access_token), // 加密存储,永不过期
status: 'ACTIVE', products: ['transactions'],
}});
游标增量同步交易(核心循环):
let cursor = item?.transactionsCursor || null;
let hasMore = true;
while (hasMore) {
const { data } = await plaidClient.transactionsSync({
access_token, cursor: cursor || undefined, count: 500, // 单次最大
});
// 处理 data.added / data.modified / data.removed ...
cursor = data.next_cursor;
hasMore = data.has_more;
}
await db.plaidItem.update({ where: { itemId }, data: { transactionsCursor: cursor } });
前端拉起:usePlaidLink({ token, onSuccess: (publicToken) => /* 回传换 token */ })。
注意事项
- access_token 永不过期但极敏感(CRITICAL):必须加密存储,绝不下发到客户端;secret 同理仅存服务端、走环境变量,禁止硬编码。
- 缓存余额不能用于支付决策(ERROR):
accountsGet是缓存数据,扣款判断改用accountsBalanceGet。 - Webhook 可能乱序/重复(HIGH):必须验签 + 幂等设计;缺签名校验视为错误。
- Item 会进入错误态(HIGH):
ITEM_LOGIN_REQUIRED需走 Link 更新模式;PENDING_DISCONNECT提前提醒用户重连;USER_PERMISSION_REVOKED要清理本地数据。 - Link token 短时单次有效(4 小时):每次会话新建,禁止缓存复用。
- Sandbox 不等于生产:Sandbox 数据简化,上线前需真实环境验证。
- 交易同步务必持久化 cursor,否则无法增量;优先 webhook 而非轮询。
互见
- 实际支付收单 → Stripe 集成(Plaid 负责连接账户,Stripe 负责扣款)
- 交易分类与预算分析 → 数据分析/分析专家
- 投资组合追踪与报表 → 数据工程
- 合规与审计(SOC2/PCI)→ 安全专家
- 移动端 → React Native Plaid SDK
采编自 sickn33/antigravity-awesome-skills(MIT),原 skill 上游标注源 vibeship-spawner-skills(Apache 2.0)。本条为适配重写,非逐字翻译。