我正在尝试使用 Stripe 和 NextJS 13.2.3 设置 webhook

qui*_*gly 7 stripe-payments typescript next.js next.js13

我能够通过内置 Stripe 页面成功结帐我的购物车,并且我会被重定向到我的 successUrl 路线。我的本地测试 webhook 正在按预期被调用。但是,当我包含代码来验证请求是否来自 Stripe 时,我收到一些错误。

这是我在 NextJS 中的 webhook

import { stripe } from "@/lib/stripe"

export async function POST(req: Request){

    const payload = await req.json()
    
    const sig = req.headers.get('stripe-signature') as string
    console.log("sig: ", sig)

    let event;

    const endpointSecret = process.env.WEBHOOK_SECRET_LOCAL as string

    try {
        console.log("constructing event...")
        event = stripe.webhooks.constructEvent(payload, sig, endpointSecret)
    } catch (error) {
        console.error(error)
        return new Response(`Webhook error: ${error}`, {
            status: 400,
        })
    }
    
    return new Response("payment confirmation route received", {
        status: 200,
    })
}
Run Code Online (Sandbox Code Playgroud)

我正在使用 Stripe CLI 监听成功事件 stripe listen --forward-to localhost:3000/api/checkout/payment-confirmation

检查完我的购物车后,我发现...

2023-03-23 15:51:28 --> checkout.session.completed [evt_1MowyjE7TMviQOgJNbXch10w] 2023-03-23 15:51:28 --> charge.succeeded [evt_3MowyhE7TMviQOgJ09e3Q7Hu] 2023-03-23 15:51:29 --> payment_intent.succeeded [evt_3MowyhE7TMviQOgJ0OIjeNoH] 2023-03-23 15:51:29 --> payment_intent.created [evt_3MowyhE7TMviQOgJ0ovhouJM] 2023-03-23 15:51:32 <-- [400] POST http://localhost:3000/api/checkout/payment-confirmation [evt_3MowyhE7TMviQOgJ09e3Q7Hu] 2023-03-23 15:51:32 <-- [400] POST http://localhost:3000/api/checkout/payment-confirmation [evt_3MowyhE7TMviQOgJ0OIjeNoH] 2023-03-23 15:51:32 <-- [400] POST http://localhost:3000/api/checkout/payment-confirmation [evt_1MowyjE7TMviQOgJNbXch10w] 2023-03-23 15:51:32 <-- [400] POST http://localhost:3000/api/checkout/payment-confirmation [evt_3MowyhE7TMviQOgJ0ovhouJM]

从 NextJS 中我看到......

StripeSignatureVerificationError: Webhook payload must be provided as a string or a Buffer (https://nodejs.org/api/buffer.html) instance representing the _raw_ request body.Payload was provided as a parsed JavaScript object instead. Signature verification is impossible without access to the original signed material. Learn more about webhook signing and explore webhook integration examples for various frameworks at https://github.com/stripe/stripe-node#webhook-signing

我认为这意味着我需要以某种方式修改有效负载参数。我尝试过payload.toString()JSON.stringify(payload)使用const buf = Buffer.from(payload)和各种其他东西,认为我需要以某种方式将这个对象转换为字符串或缓冲区,但我不理解这个问题。非常感谢帮助!

- 更新 -

现在我已经更新了代码,但我仍然收到一般错误......

No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?

我对 Stripe 的回应:“我的意思是,我认为是这样?!?”

我还尝试导入“micro”或“raw-body”,正如我看到其他人使用的那样,但它们会给我带来各种错误,这就是为什么我使用来自 node.js 的错误。req.body 是一个 ReadableStream,

类型为“ReadableStream |”的参数 null' 不可分配给'string | 类型的参数 缓冲'

如果我尝试将其作为构造事件中的第一个参数传递。

import { stripe } from "@/lib/stripe"
import { NextRequest } from "next/server";
import { Buffer } from "node:buffer";

export async function POST(req: NextRequest){
    
    const buf = Buffer.from(req.toString())
// <Buffer 5b 6f 62 6a 65 63 74 20 52 65 71 75 65 73 74 5d>

    const headers = req.headers
    
    const sig = req.headers.get("stripe-signature") as string | string[]
// t=1679...,v1=888050e17...,v0=cc9b94a...
    let event;
    const endpointSecret = process.env.WEBHOOK_SECRET_LOCAL as string
// whsec_...
    

    try {
        console.log("constructing event...")
        event = stripe.webhooks.constructEvent(buf, sig, endpointSecret)
    } catch (error) {
        console.error(error)
        return new Response(`Webhook error: ${error}`, {
            status: 400,
        })
    }
    
    return new Response("payment confirmation route received", {
        status: 200,
    })
}

export const config = {
    api: {
      bodyParser: false,
    },
  };
Run Code Online (Sandbox Code Playgroud)

小智 13

不用使用micro来缓冲主体,只需使用await req.text()将原始主体转换为字符串,然后将字符串传递给stripe.webhooks.constructEvent.


aar*_*rse 6

这对我有用。如果您有任何疑问,请告诉我。

\n
import Stripe from 'stripe';\nimport { headers } from 'next/headers';\n\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY!,\n  {\n    apiVersion: '2022-11-15'\n  }\n)\n\nexport async function POST(req: Request) {\n  const body = await req.text();\n  const sig = headers().get('Stripe-Signature') as string;\n  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;\n  let event: Stripe.Event;\n\n  try {\n    if (!sig || !webhookSecret) return;\n    event = stripe.webhooks.constructEvent(body, sig, webhookSecret);\n  } catch (err: any) {\n    console.log(`\xe2\x9d\x8c Error message: ${err.message}`);\n    return new Response(`Webhook Error: ${err.message}`, { status: 400 });\n  }\n\n  if (relevantEvents.has(event.type)) {\n    try {\n      switch (event.type) {\n        case 'product.created':\n          break;\n        case 'product.updated':\n          break;\n        default:\n          throw new Error('Unhandled relevant event!');\n      }\n    } catch (error) {\n      console.log(error);\n      return new Response('Webhook handler failed. View logs.', {\n        status: 400\n      });\n    }\n  }\n  return new Response(JSON.stringify({ received: true }));\n}\n
Run Code Online (Sandbox Code Playgroud)\n


koo*_*jah 1

当 Stripe 将事件发送到您的 webhook 端点时,它们首先根据事件的原始正文计算签名。处理数据时,您需要确保查看的是 Stripe 使用的相同原始主体。如果您的代码查看修改后的版本,即使它只是添加了一个空格或更改了属性的顺序,您也将无法匹配签名。

使用 stripe-node 时,开发人员经常会遇到签名验证问题。问题是,各种框架在收到 JSON 并已经解析它以供使用时试图提供帮助。因此,当您尝试访问数据时,即使其中的“数据”相似,您最终也会得到不同版本的有效负载。

您的代码正在使用req.json(),但您想要的是访问 POST 请求的确切原始正文,这通常是通过request.body. 您可以在这里参考 Stripe 的文档。查看Stripe-node 的 Github 存储库上的这个流行问题也很有用,它有许多替代解决方案,具体取决于您的整体环境。