如何通过Stripe CLI传递客户邮箱?及支付字段访问确认
问题1:如何通过Stripe CLI传递客户邮箱
你可以通过Stripe CLI的--override参数自定义触发事件的payload字段,或者提前创建带邮箱的测试客户关联到事件中,两种方式任选:
方式1:直接覆盖事件中的邮箱字段
如果触发的是payment_intent.succeeded事件,可以直接修改付款记录的账单详情邮箱,无需提前创建客户:
stripe trigger payment_intent.succeeded --override payment_intent.charges.data[0].billing_details.email=test@example.com
方式2:关联带邮箱的测试客户
- 先创建一个带指定邮箱的测试客户:
stripe customers create --email test@example.com
执行后会返回一个客户ID(格式如cus_xxx123)。
2. 触发事件时将PaymentIntent关联到这个客户:
stripe trigger payment_intent.succeeded --override payment_intent.customer=cus_xxx123
此时webhook事件中的PaymentIntent会包含该客户ID,你可以通过Stripe SDK调用stripe.customers.retrieve(cus_xxx123)获取邮箱。
注意:你代码中监听的是
payment_intent.succeeded事件,但之前提到用stripe trigger invoice.payment_succeeded,这两个事件对应的数据对象完全不同(一个是PaymentIntent,一个是Invoice),要保证事件类型和监听逻辑匹配。
问题2:event.data.object字段访问的正确性及生产环境验证
你的代码存在两处关键问题,生产环境也会报错:
1. 类型断言错误
你监听的是payment_intent.succeeded事件,event.data.object的实际类型是PaymentIntent,但你断言成了Invoice,这会导致TypeScript类型提示错误,且运行时无法找到Invoice专属字段。
2. 字段匹配错误
id:PaymentIntent确实有id字段,这个是正确的,但注意这是PaymentIntent的ID,不是Invoice ID。amount_paid:这是Invoice的专属字段,PaymentIntent对应的字段是amount_captured(已成功捕获的金额),你访问amount_paid会返回undefined。customer_email:PaymentIntent中没有这个直接字段,获取邮箱有两种方式:- 如果PaymentIntent关联了客户(即存在
customer字段),需通过该客户ID调用Stripe API获取客户详情,从中提取email; - 如果是客人付款(未创建客户),可从
payment_intent.charges.data[0].billing_details.email获取邮箱。
- 如果PaymentIntent关联了客户(即存在
更适合你的优化方案
因为你用的是Stripe Checkout,推荐直接监听checkout.session.completed事件——这个事件是Checkout付款完成后触发的,data.object(CheckoutSession)中直接包含customer_email字段,无需额外查询:
const buy = async (req: Request, res: IResponse): Promise<IResponse> => { try { const signature = req.headers["stripe-signature"]; const event = stripe.webhooks.constructEvent( req.rawBody, signature, process.env.STRIPE_WEBHOOK_SECRET ); if (event.type === "checkout.session.completed") { const session = event.data.object as Stripe.Checkout.Session; const { id: sessionId, customer_email, amount_total } = session; const serialNumber = await PaymentControler.generateUniqueSerialNumber(); await PaymentControler.updateUserMembership( customer_email, sessionId, // 或取session.payment_intent拿到PaymentIntent ID amount_total, serialNumber ); return res.sendStatus(200); } return res.sendStatus(200); // 非目标事件直接返回200,避免Stripe重复重试 } catch (err) { return res.status(400).send(`Webhook Error: ${err.message}`); // 返回400让Stripe重试,500会终止重试 } };
生产环境验证
生产环境中,只要你前端Checkout配置了customerEmail,checkout.session.completed事件的customer_email字段一定会存在;如果用payment_intent.succeeded,只要付款流程正常,amount_captured和customer/charges字段也会正常返回,不存在测试环境和生产环境的字段差异。
内容的提问来源于stack exchange,提问作者HelloWorldEngineer

