如何区分Stripe中customer.subscription.updated事件的新旧订阅信息
先对比你提供的两个事件内容(已翻译为中文):
旧套餐订阅事件对象
{
"id": "sub_NA2KPh90",
"object": "subscription",
"status": "canceled",
"cancel_at_period_end": true,
"current_period_end": 1699823999,
"customer": "cus_XXXXXX",
"items": {
"data": [
{
"id": "si_XXXXXX",
"price": {
"id": "price_OLD",
"nickname": "基础版套餐",
"currency": "usd",
"unit_amount": 1000
}
}
]
},
"created": 1697231999,
"ended_at": 1699823999
}
新套餐订阅事件对象
{
"id": "sub_qF9g1wDs",
"object": "subscription",
"status": "active",
"cancel_at_period_end": false,
"current_period_end": 1702415999,
"customer": "cus_XXXXXX",
"items": {
"data": [
{
"id": "si_YYYYYY",
"price": {
"id": "price_NEW",
"nickname": "专业版套餐",
"currency": "usd",
"unit_amount": 2000
}
}
]
},
"created": 1699824000,
"start_date": 1699824000
}
从以上内容的核心差异出发,你可以通过以下方法过滤旧事件,只处理新套餐的更新:
- 筛选活跃状态的订阅:旧事件的
status为canceled,新事件为active,直接忽略status不是active的事件。 - 检查到期取消标记:旧事件的
cancel_at_period_end为true(表示该订阅会在当前周期结束后取消),新事件为false,可作为辅助过滤条件。 - 对比价格ID:新事件的
items.data[0].price.id与旧事件不同,结合你数据库中存储的客户当前价格ID,只有当事件中的价格ID与数据库不一致时,才执行更新操作。 - 订阅时间字段:旧事件包含
ended_at(订阅结束时间),新事件包含start_date(新订阅开始时间),可通过字段存在性快速区分。
示例代码(Node.js)
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); app.post('/stripe-webhook', async (req, res) => { const sig = req.headers['stripe-signature']; let event; // 验证Webhook签名,防止伪造事件 try { event = stripe.webhooks.constructEvent( req.rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { return res.status(400).send(`Webhook Error: ${err.message}`); } if (event.type === 'customer.subscription.updated') { const subscription = event.data.object; // 过滤条件:仅处理活跃、非到期取消的订阅 if (subscription.status !== 'active' || subscription.cancel_at_period_end) { return res.sendStatus(200); } // 从数据库获取客户当前的套餐信息 const customer = await getCustomerByStripeId(subscription.customer); if (!customer) return res.sendStatus(200); const newPriceId = subscription.items.data[0].price.id; // 如果价格ID有变化,更新数据库 if (customer.subscription_price_id !== newPriceId) { await updateCustomerSubscription(customer.id, { subscription_price_id: newPriceId, plan_name: subscription.items.data[0].price.nickname, current_period_end: subscription.current_period_end }); } } res.sendStatus(200); });
关键提醒
- 必须验证Stripe Webhook的签名,这是防止恶意数据篡改的核心步骤。
- 如果你的业务允许客户拥有多个订阅,需要结合
subscription.id和客户ID进行更精准的匹配,避免更新错误的订阅信息。 - 对于套餐变更后立即生效的场景,旧订阅会直接被标记为
canceled,此时仅通过status字段即可完成过滤。
内容的提问来源于stack exchange,提问作者Walter

