stripe.checkout.sessions.create 能指定 webhook 接收目标吗?
创建 Session 时,不能通过 stripe.checkout.sessions.create(...) 选择 webhook destination。该接口没有接收 destination 名称、we_... ID 或 webhook URL 的参数,webhook 接收地址需要单独配置。参见 Checkout Session 创建参数。
如果两个目标都已启用,处于相同的测试或正式环境,监听相同的账户事件来源,且订阅了此次产生的事件类型,那么两个目标都会收到该事件。Stripe 不会根据购买发生在哪个网站,自动只通知对应网站的接口。Stripe 官方说明指出,事件会发送给所有配置为接收该事件类型的 endpoint。
为什么 destination 名称和 ID 不能选择接收方?
mydestination-mymarket 是配置名称,we_L0w3rCaS3AndUpperCas3Mess 是 webhook endpoint 的标识。它们用于识别和管理接收目标,不会自动与网站或某次 Checkout Session 建立绑定关系。
Stripe 根据目标配置中的事件来源、订阅类型等条件投递事件。即使两个目标使用不同域名,只要配置条件相同,也可能收到同一个事件。相关设置见 webhook 配置文档。
success_url 用于支付流程结束后的浏览器跳转,不能用来指定 webhook 地址。
两个网站共用账户时,如何区分事件?
可以在创建 Checkout Session 时写入网站标识,由服务器在收到事件后判断归属。常见做法有两种:
- 使用一个统一的 webhook 接口,根据网站标识分派给对应业务。
- 保留两个 webhook 接口,每个接口验证签名后,只处理属于自己网站的事件。
采用第二种方式时,两个接口仍会收到匹配的事件;代码中的判断只决定是否执行后续业务。
创建 Session 时加入网站标识
下面以 Node.js 的一次性支付为例。环境变量、Price 和域名需要替换为实际配置,网站标识应由服务器根据可信的业务上下文确定。
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [
{ price: process.env.STRIPE_PRICE_ID, quantity: 1 }
],
success_url: 'https://site-a.example/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://site-a.example/cart',
metadata: {
site_id: 'site-a'
}
});
对于包含 Checkout Session 对象的 checkout.session.completed 事件,可以从 event.data.object.metadata 读取该标识。Stripe 文档支持这种用法,见 metadata 与 webhook 事件。
验证签名后按网站筛选
以下 Express 示例沿用上面的 stripe 实例,只演示签名验证和归属判断,订单处理需要自行实现。
import express from 'express';
const app = express();
// Register this route before middleware that parses JSON bodies.
app.post('/webhooks/site-a', express.raw({ type: 'application/json' }), (req, res) => {
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
req.headers['stripe-signature'],
process.env.STRIPE_WEBHOOK_SECRET_SITE_A
);
} catch {
return res.sendStatus(400);
}
if (event.type !== 'checkout.session.completed') {
return res.sendStatus(200);
}
const checkoutSession = event.data.object;
if (checkoutSession.metadata?.site_id !== 'site-a') {
return res.sendStatus(200);
}
// TODO: Check payment status and process the order idempotently.
return res.sendStatus(200);
});
app.use(express.json());
网站 B 的接口可以采用相同逻辑,将匹配值改为 site-b,并使用网站 B 对应 endpoint 的签名密钥。对验签成功但不属于本站的事件返回 2xx,即可确认接收,无须让 Stripe 重试。原始请求体和 endpoint 签名密钥的要求见 webhook 文档。
接入时还要注意什么?
we_...是 endpoint ID;验证签名需要的是对应目标的whsec_...签名密钥。不同 endpoint 应使用各自的密钥。- Session 的
metadata不会自动复制到所有相关对象。如果改为处理payment_intent.succeeded,需要根据业务使用payment_intent_data.metadata,或通过数据库查询归属。见 metadata 传递规则。 checkout.session.completed不一定表示延迟支付已经成功。实际履约要检查payment_status;需要支持延迟支付时,还应处理checkout.session.async_payment_succeeded。- 订单处理必须保证幂等,避免重试或重复通知造成重复发货、重复开通服务。支付状态判断和履约要求见 Checkout 履约指南。
备注:内容仅供参考。
