Stripe
快速开始
使用 Stripe 蓝图为已有的 Flue 项目添加已验证的 webhook 入口和应用自有的 API 行为。在终端或你选择的编码智能体中运行以下命令:
flue add channel stripe
概述
该蓝图安装 @flue/stripe、Stripe 官方的 stripe SDK 以及所需的 TypeScript 对等依赖(如有需要)。它在 <source-root>/channels/stripe.ts 创建文件,其中命名的 channel 导出默认验证快照 webhook 事件,项目自有的 client 处理出站 API 调用。根据应用调整所选事件、智能体和工具。
import Stripe from 'stripe';
import { createStripeChannel } from '@flue/stripe';
import { dispatch, useModel } from '@flue/runtime';
import { Billing } from '../agents/billing.ts';
export const client = new Stripe(process.env.STRIPE_SECRET_KEY!, {
httpClient: Stripe.createFetchHttpClient(),
});
export const channel = createStripeChannel({
client,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
async webhook({ event }) {
if (event.type !== 'checkout.session.completed') return;
const session = event.data.object;
const customerId =
typeof session.customer === 'string' ? session.customer : session.customer?.id;
if (!customerId) return;
await dispatch(Billing, {
id: customerId,
message: {
kind: 'signal',
type: `stripe.${event.type}`,
body: `Checkout session ${session.id} reported payment status ${session.payment_status}.`,
attributes: {
eventId: event.id,
customerId,
sessionId: session.id,
paymentStatus: session.payment_status,
...(session.amount_total === null ? {} : { amountTotal: String(session.amount_total) }),
...(session.currency === null ? {} : { currency: session.currency }),
},
},
});
},
});匹配的事件被接受到由其 Stripe 客户标识的计费智能体。其他事件收到空的成功响应。生成的模块还定义了一个客户绑定的检索工具;蓝图将该工具连接到计费智能体。对于 Cloudflare 目标,相同的 SDK 使用其 Fetch 和 Web Crypto 实现。
挂载渠道
渠道仅在 app.ts 挂载它的地方提供 HTTP 路由。挂载模块的命名 channel 导出:
import { channel as stripe } from './channels/stripe.ts';
app.route('/channels/stripe', stripe.route());channel.route() 是一个纯路由器工厂,相对于挂载路径提供渠道声明的路由。本指南中的 webhook 路径假定使用常规的 /channels/stripe 挂载路径;不同的挂载路径会相应调整。分发目标智能体模块携带 'use agent' 指令——该指令会注册它,因此仅用于分发的智能体不需要自己的 HTTP 挂载。
配置
| 变量 | 用途 |
|---|---|
STRIPE_WEBHOOK_SECRET | 必需 — 验证入站交付。 |
STRIPE_SECRET_KEY | 必需 — 验证出站 SDK 调用。 |
它安装 @flue/stripe 和 Stripe 官方的 stripe SDK。SDK 验证入站 payload,并作为项目自有的客户端处理出站 API 调用。蓝图创建 src/channels/stripe.ts,包含命名的 channel 和 client 导出。
将 Stripe 事件目标配置为:
https://example.com/channels/stripe/webhook
如果 flue() 挂载在外层前缀之下,请包含该前缀。仅订阅应用处理的事件类型。将两个凭据保存在项目现有的密钥系统中。
渠道模块
快照事件是默认的:
import Stripe from 'stripe';
import { createStripeChannel } from '@flue/stripe';
import { defineTool, dispatch, useModel } from '@flue/runtime';
import { Billing } from '../agents/billing.ts';
export const client = new Stripe(process.env.STRIPE_SECRET_KEY!, {
httpClient: Stripe.createFetchHttpClient(),
});
export const channel = createStripeChannel({
client,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
// 路径:/channels/stripe/webhook
async webhook({ event }) {
switch (event.type) {
case 'checkout.session.completed':
case 'checkout.session.async_payment_succeeded': {
const session = event.data.object;
const customerId =
typeof session.customer === 'string' ? session.customer : session.customer?.id;
if (!customerId) return;
await dispatch(Billing, {
id: customerId,
message: {
kind: 'signal',
type: `stripe.${event.type}`,
body: `Checkout session ${session.id} reported payment status ${session.payment_status}.`,
attributes: {
eventId: event.id,
customerId,
sessionId: session.id,
paymentStatus: session.payment_status,
...(session.amount_total === null
? {}
: { amountTotal: String(session.amount_total) }),
...(session.currency === null ? {} : { currency: session.currency }),
},
},
});
return;
}
default:
return;
}
},
});
export function retrieveCustomer(customerId: string) {
return defineTool({
name: 'retrieve_stripe_customer',
description: '检索绑定到此计费智能体的 Stripe 客户。',
async run() {
const customer = await client.customers.retrieve(customerId);
return {
output:
'deleted' in customer
? { id: customer.id, deleted: true }
: { id: customer.id, name: customer.name, email: customer.email },
};
},
});
}@flue/stripe 在调用 webhook 之前将精确的请求字节和 Stripe-Signature 头提供给 Stripe 的 SDK。不返回任何内容会产生一个空的 200 响应。兼容 JSON 的值会变成响应体,普通的 Hono 或 Fetch Response 会原样传递。
该示例使用客户 ID 作为单个 Stripe 账户的智能体实例 ID。Stripe 没有通用的对话标识。选择代表应用工作单元的客户、订阅、账户、Checkout Session 或其他资源。当资源命名空间要求时,Connect 和组织目标应包含已验证的 event.account 或 event.context。
绑定工具
'use agent';
import { type AgentProps, useModel, useTool } from '@flue/runtime';
import { retrieveCustomer } from '../channels/stripe.ts';
export function Billing({ id: customerId }: AgentProps) {
useModel('anthropic/claude-haiku-4-5');
useTool(retrieveCustomer(customerId));
return 'Review the completed Checkout event and summarize any billing follow-up that is needed.';
}模型可以调用查找但不能选择另一个客户、账户或凭据。受信任的应用代码绑定这些值。渠道 - 智能体导入循环是受支持的,因为两个导入的绑定在延迟回调或智能体函数体内都是只读的。
精简事件通知
仅当事件目标配置为发送 Stripe 精简事件通知时,设置 eventPayload: 'thin':
export const channel = createStripeChannel({
client,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
eventPayload: 'thin',
// 路径:/channels/stripe/webhook
async webhook({ event }) {
const relatedObject = await event.fetchRelatedObject();
await handleRelatedObject(event, relatedObject);
},
});
回调接收 Stripe 原生的 Stripe.V2.Core.EventNotification。其 fetchEvent() 和 fetchRelatedObject() 方法使用项目自有的客户端并保留 Stripe 上下文。快照和精简目标使用不同的签名 payload 格式;包会拒绝与配置模式不匹配的 payload。
交付行为
Stripe 在最多三天内重试失败的实时模式 webhook 交付,在几小时内重试沙箱交付三次。排序不保证且可能重复交付。当不允许重复入站时,在分发之前在应用自有的持久存储中声明 event.id。业务操作应保持幂等,因为单独的 Event 对象可能描述相同的资源变更。
快照事件对象绑定到事件目标选择的 API 版本。保持该版本与已安装的 SDK 类型一致,或在应用代码中缩小和验证资源字段。
Flue 转发比已安装 Stripe 声明更新的已验证事件类型。在项目升级 Stripe 之前,使用 switch (event.type as string) 来观察该未来类型,并将其资源字段视为不受信任,而不是削弱每个已知事件的原生缩小。
官方 Stripe SDK 在 Cloudflare Workers 中选择其 Fetch 和 Web Crypto 实现。该示例在 workerd 中使用 Flue 所需的 nodejs_compat 配置执行该路径。项目可以通过 process.env 或类型化的 Worker 绑定初始化凭据,但仍应验证其完整的目标构建和 workerd 测试。Stripe 的声明引用了 @types/node;该包仅是类型依赖,不会向 Worker 包添加 Node 代码。
渠道不注册事件目标、不轮换签名密钥、不管理 OAuth 或 API 密钥、不去重事件、不恢复排序,也不定义通用 Stripe 工具。它也不声明支持专门的延迟敏感工作流,如同步实时 Issuing 授权决策。