You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 03:47:35