Stripe Webhook Session字段访问返回Undefined问题排查
问题根源与解决方案
核心问题:类型断言不匹配
你处理的是charge.succeeded事件,该事件的event.data.object是Stripe Charge对象,但你错误地将其断言为Stripe.Checkout.Session类型。这直接导致:
- TypeScript自动补全显示Session类型的字段(如
shipping_details),但实际对象是Charge,仅包含shipping字段 - 访问
session.shipping_details返回undefined,因为Charge对象中不存在该属性
从你打印的结构也能验证这一点:object字段的值是'charge',完全说明当前对象是Charge而非Checkout Session。
修复步骤
1. 修正类型断言
将event.data.object的类型断言改为Stripe.Charge:
// 替换原有的Session断言 const charge = event.data.object as Stripe.Charge;
2. 正确访问配送信息
Charge对象的配送信息存储在shipping字段下,调整访问方式:
console.log(charge.shipping?.address?.state); // 可正确获取到'TX'
3. 可选优化:切换到对应事件类型
如果你的业务逻辑依赖Checkout Session的完整数据,建议监听checkout.session.completed事件——该事件的event.data.object才是真正的Stripe.Checkout.Session,此时访问session.shipping_details才符合预期:
if (event.type === "checkout.session.completed") { const session = event.data.object as Stripe.Checkout.Session; console.log(session.shipping_details?.address?.state); // 正确获取Session的配送信息 // 后续订单处理逻辑... }
修复后的完整代码片段
import Stripe from "stripe"; import { stripe } from "@/lib/stripe"; import { headers } from "next/headers"; import { NextResponse } from "next/server"; import prisma from "@/lib/db/prisma"; import { OrderItem } from "@prisma/client"; import { createCart, getCart } from "@/lib/db/cart"; import { revalidatePath } from "next/cache"; export async function POST(req: Request) { const cart = (await getCart()) ?? (await createCart()); const body = await req.text(); const signature = headers().get("Stripe-Signature") as string; let event: Stripe.Event; try { event = stripe.webhooks.constructEvent( body, signature, process.env.STRIPE_WEBHOOK_SECRET!, ); } catch (error) { return new NextResponse("Invalid signature", { status: 400 }); } if (event.type === "charge.succeeded") { const charge = event.data.object as Stripe.Charge; console.log("*********************************************************"); console.log(charge.shipping?.address?.state); console.log("*********************************************************"); const userId = charge.metadata.userId; const { name, address, aptNumber, city, state, zipCode, country, paymentMethod, totalInCents, taxInCents, cartItems, } = charge.metadata; try { await prisma.$transaction(async (prisma) => { const order = await prisma.order.create({ data: { userId, name, address, aptNumber, city, state, zipCode, country, paymentMethod, totalInCents: parseInt(totalInCents), taxInCents: parseInt(taxInCents), }, }); const orderItems = JSON.parse(cartItems).map((item: OrderItem) => ({ productId: item.productId, productName: item.productName, price: item.price, quantity: item.quantity, orderId: order.id, })); await prisma.orderItem.createMany({ data: orderItems, }); await prisma.cartItem.deleteMany({ where: { cart: { userId: userId, }, }, }); await prisma.cart.update({ where: { id: cart.id }, data: {}, }); revalidatePath("/", "layout"); }); } catch (error) { console.error("Error handling checkout session:", error); } } return new NextResponse("ok", { status: 200 }); }
额外说明
- Stripe不同事件类型对应不同的对象结构,必须根据事件类型匹配正确的TypeScript类型
- 建议在Stripe Dashboard的Webhook设置中,仅订阅业务所需的事件类型,避免逻辑混淆
内容的提问来源于stack exchange,提问作者Frank Muller
相关产品推荐
相关产品推荐

