无法将数据从 Stripe webhook 发送到 Firebase

Posted

技术标签:

【中文标题】无法将数据从 Stripe webhook 发送到 Firebase【英文标题】:Unable to send data from Stripe webhook to Firebase 【发布时间】:2021-09-30 16:37:30 【问题描述】:

我创建了一个 Stripe webhook,我想在购买 Stripe 时将数据写入 Firebase,但它无法正常工作,尽管付款总是成功,但数据并未发送到 Firebase 数据库。

在 Stripe 控制台中,我收到以下错误:

Webhook 错误:未找到与有效负载的预期签名匹配的签名。您是否传递了从 Stripe 收到的原始请求正文? https://github.com/stripe/stripe-node#webhook-signing

以下是我的 webhook 的代码:

import  buffer  from "micro";
import * as admin from "firebase-admin";

// Secure a connection to firebase
const serviceAccount = require("../../../permession.json");
const app = !admin.apps.length
  ? admin.initializeApp(
      credential: admin.credential.cert(serviceAccount),
    )
  : admin.app();

// Stripe

const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

const endpointSecurit = process.env.STRIPE_SIGNING_SECRET;

const fullfillOrder = async (session) => 
  console.log("Fullfilling Order!!!");

  return app
    .firestore()
    .collection("users")
    .doc(session.metadata.email)
    .collection("orders")
    .doc(session.id)
    .set(
      amount: session.amount_total / 100,
      amount_shipping: session.total_details_amount_shipping / 100,
      images: JSON.parse(session.metadata.images),
      title: JSON.parse(session.metadata.titles),
      timestamp: admin.firestore.FieldValue.serverTimestamp(),
    )
    .then(() => 
      console.log(`SUCCESS: Order $session.id has been added to DB!`);
    );
;

export default async (req, res) => 
  if (req.method === "POST") 
    const requestBuffer = await buffer(req);
    const payload = requestBuffer.toString();
    const sig = req.headers["stripe-signature"];

    let event;

    // Verify (came from stripe)
    try 
      event = await stripe.webhooks.constructEvent(
        payload,
        sig,
        endpointSecurit
      );
     catch (e) 
      console.log("ERROR", e.message);
      return res.status(400).send( message: "Webhook error: " + e.message );
    
    if (event.type === "checkout.session.completed") 
      const session = event.data.object;

      // Fullfill the order
      return fullfillOrder(session)
        .then(() => res.status(200))
        .catch((e) =>
          res.status(400).send( message: "WEBHOOK_ERROR: " + e.message )
        );
    
  
;

export const config = 
  api: 
    bodyParser: false,
    externalResolver: true,
  ,
;

感谢您的帮助。

【问题讨论】:

你可以试试const payload = req.rawBody 吗? @Dharmaraj 我试过了,但它给出了同样的错误。 你能与 req.rawBody 分享更新的代码吗? const event = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret); 这应该可以。 在 Node 中获取实际的原始主体非常困难,因为主体很容易在您获得它之前进行修改。如果上面提出的解决方案不起作用,这里有很多不同的可能解决方案来解决这个问题:github.com/stripe/stripe-node/issues/341 【参考方案1】:

如果此代码已托管在 Google Cloud Functions 上,您必须考虑在处理请求之前由 Functions Framework(GCF docs、Firebase docs)完成的自动正文解析转到 Next.js。因为 body 已经被消费了,bodyParser: false 指令对于已经处理的类型是没有意义的。

为了帮助依赖它的模块,框架将请求未解析主体的Buffer 附加为属性req.rawBody

如果您使用的是 linter 或 TypeScript,req 对象可能会被键入为 express.Request 对象,这可能会报告此 rawBody 属性不存在,因为它不再是 Express 核心的一部分.您需要将其合并到类型中或将类型覆盖为 express.Request | rawBody: Buffer 以使其静音。

从您的代码中提取相关行:

const requestBuffer = await buffer(req);
const payload = requestBuffer.toString();
const sig = req.headers["stripe-signature"];

const event = await stripe.webhooks.constructEvent(
  payload,
  sig,
  endpointSecurit
);

应该替换为

const sig = req.headers["stripe-signature"];

const event = await stripe.webhooks.constructEvent(
  req.rawBody.toString(),
  sig,
  endpointSecurit
);

确保还检查您的环境变量是否已正确配置。

const endpointSecurit = process.env.STRIPE_SIGNING_SECRET;
if (typeof endpointSecurit !== "string") 
  console.error("Unexpected value for STRIPE_SIGNING_SECRET");
  // potentially throw an error here

旁注

如果使用 Cloud Functions 的 Firebase 分支,您还可以使用 functions.config() 来处理您的机密,而不是使用环境变量。

const STRIPE_SECRET = functions.config().stripe.secret;
const STRIPE_WEBHOOK_SECRET = functions.config().stripe.webhook_secret;

您不应默默地忽略不是使用POST 发送的请求,而应以相应的错误进行响应:

if (req.method !== "POST") 
  res.status(405).set('Allow', 'POST').send("");
  return;

不要忘记使用.end().send() 等来结束您的请求!

res.status(200).send("");

如果您开始处理多种 webhook 事件类型,请考虑改用事件处理程序对象来帮助组织您的代码,而不是使用大型 if-else/switch 树:

// before the route handler
const eventHandlers = ; // Record<string, (event, req, res) => Promise<unknown> | unknown>

eventHandlers["checkout.session.completed"] = async (event, req, res) => 
  const session = event.data.object;
  return fullfillOrder(session)
    .then(() => res.status(200))
    .catch((e) =>
      res.status(400).send( message: "WEBHOOK_ERROR: " + e.message )
    );

// in the route handler
const handler = eventHandlers[event.type];
if (!handler) 
  console.error('Ignored unexpected event type of ' + event.type);
  res.status(200).send("");
  return;


try 
  await Promise.resolve(handler(event, req, res))
 catch (err) 
  console.error("Unexpected error in event handler for event type of ' + event.type, err);
  res.status(500).send( message: "Internal server error" );

【讨论】:

以上是关于无法将数据从 Stripe webhook 发送到 Firebase的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 ngrok 测试接收 Stripe webhook

curl 作为 Zapier Webhook 到 Stripe

如何识别 Stripe webhooks 类型?

Stripe Webhooks - 我可以发送 200 以外的响应代码吗?

从 Stripe webhook .Net 中提取元数据

使用 Stripe Webhook 发送获取客户详细信息以使用 Stripe 进行定期付款