我一直在尝试在 Express.js 应用程序中实现 Stripe Webhooks,但我遇到了 Webhook 签名验证问题。我收到的错误消息是:
⚠️ Webhook signature verification failed. 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.
这是我当前的设置:
index.js
require("dotenv").config();
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
const port = 8080;
const cors = require("cors");
const mongoose = require("mongoose");
app.use(cors());
app.get("/", (req, res) => {
res.send("Hello, World!");
});
// Use these parsers for other routes
app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ limit: "50mb", extended: true }));
const dbURI = process.env.MONGODB_CONNECTION;
const searchRouter = require("./routes/searchRoutes");
const phoneRouter = require("./routes/phone");
const paymentRouter = require("./routes/payment");
const linkRouter = require("./routes/link");
const pieceRouter = require("./routes/piece");
const userRouter = require("./routes/user");
const emailRouter = require("./routes/email");
const autocompleteRouter = require("./routes/autocomplete");
const webhookRouter = require("./routes/webhook");
app.use("/", searchRouter);
app.use("/phone", phoneRouter);
app.use("/payment", paymentRouter);
app.use("/link", linkRouter);
app.use("/piece", pieceRouter);
app.use("/user", userRouter);
app.use("/email", emailRouter);
app.use("/autocomplete", autocompleteRouter);
app.use("/webhook", webhookRouter);
mongoose
.connect(dbURI)
.then(() => {
console.log("MongoDB connected");
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}/`);
});
})
.catch((err) => console.log(err));
webhook.js
require("dotenv").config();
const express = require("express");
const router = express.Router();
const stripe = require("stripe")(process.env.STRIPE_SECRET_TEST_KEY);
const endpointSecret = process.env.STRIPE_TEST_WEBHOOK_KEY;
router.post("/", async (req, res) => {
const sig = req.headers["stripe-signature"];
console.log("Webhook received!");
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
} catch (err) {
console.log(`⚠️ Webhook signature verification failed.`, err.message);
return res.sendStatus(400);
}
if (event.type === "checkout.session.completed") {
const session = event.data.object;
try {
const paymentIntent = await stripe.paymentIntents.retrieve(
session.payment_intent,
{
expand: ["charges"],
}
);
const charge = paymentIntent.charges.data[0];
const transferId = charge.transfer;
const taxAmount = session.total_details.amount_tax;
await stripe.transfers.createReversal(transferId, {
amount: taxAmount,
});
console.log(`Successfully reversed tax amount: ${taxAmount}`);
} catch (error) {
console.error("Error creating transfer reversal:", error);
}
}
res.sendStatus(200);
});
module.exports = router;
我在这里缺少什么?如何正确处理 Stripe webhook 签名验证的原始请求正文?
到目前为止,我已经尝试了许多中间件并查看了几篇文章,但似乎没有一个对我有用。我不知道这是否重要,但我一直在使用 ngrok 公开本地端点以进行测试。
已经检查过这些文章: Stripe - Webhook 负载必须以字符串或缓冲区的形式提供 https://github.com/stripe/stripe-node/issues/341 https://github.com/stripe/stripe-node#webhook-signing Stripe Webhook 400 错误。原始请求正文问题
您有 bodyParser,这对于 Express 和 Stripe Webhook 签名来说是有问题的。查看此问题并尝试建议的解决方法(假设您将端点重命名为
/stripe-webhooks
)
app.use(bodyParser.json({
// Because Stripe needs the raw body, we compute it but only when hitting the Stripe callback URL.
verify: function(req,res,buf) {
var url = req.originalUrl;
if (url.startsWith('/stripe-webhooks')) {
req.rawBody = buf.toString()
}
}}));
或避免使用 bodyParser 并严格遵循 Stripe 提供的示例代码。