PAYATHON 2026

Firebase Cloud Function 超时结束而非 Status OK

支付阿杰

结论

HTTPS Cloud Function 超时的原因是:请求处理完成后,函数没有结束 HTTP 响应。每条执行路径都必须调用 res.send()、res.json()、res.redirect() 或 res.end()。

Property 'send' does not exist on type 'Response' 表示 TypeScript 使用了错误的 Response 类型。常见原因是浏览器 Fetch API 的全局 Response 类型,或其他依赖中的同名类型覆盖了正确类型。显式使用 Express 的 Response 类型即可。

推荐写法

import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import type { Response } from "express";

admin.initializeApp();

export const ccavRes = functions.https.onRequest(
  async (
    req: functions.https.Request,
    res: Response
  ): Promise<void> => {
    try {
      const ccavEncResponse = req.rawBody.toString("utf8");

      // 解析、解密并验证支付结果
      // const ccavPOST = qs.parse(ccavEncResponse);
      // const ccavResponse = decrypt(...);
      // await admin.firestore().collection(...).doc(...).set(...);

      res.status(200).send("OK");
      return;
    } catch (error) {
      console.error("Failed to process payment response:", error);

      res.status(500).send("Failed to process payment response");
      return;
    }
  }
);

这里没有写成:

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

某些版本允许这种写法,但较新的函数类型通常要求处理器返回 void 或 Promise<void>,而 res.send() 返回的是 Express Response 对象。先发送响应,再单独 return,能兼容更多版本:

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

解决 Response 类型冲突

先确认使用的是 Express 类型:

import type { Response } from "express";

不要从以下来源导入 Response:

import { Response } from "node-fetch";

也不要把参数显式声明为浏览器 Fetch API 的 Response:

async (req: Request, res: Response) => {
  // 此处的 Response 可能是 WHATWG Fetch Response
}

如果项目尚未安装 Express 类型,可以在 Functions 项目目录中运行:

npm install --save-dev @types/express

如果当前 Express 版本已经自带类型,就不需要额外安装。同时检查 package.json 中的 firebase-functions、typescript、express 和 @types/express,确认它们之间没有明显的主版本冲突。

也可以完全交给 Firebase 推断类型:

export const ccavRes = functions.https.onRequest(async (req, res) => {
  res.status(200).send("OK");
});

正常情况下,res 会被推断为 Express Response。如果这段最小代码仍然提示没有 send,通常可以判断是依赖类型冲突、错误导入了同名类型,或者 firebase-functions 的类型声明过旧。

为什么原函数会超时

async 函数执行完 JavaScript 代码,不代表 HTTP 请求已经结束。对于 functions.https.onRequest(),Cloud Functions 会一直等待响应流关闭。

下面的代码不会结束 HTTP 请求:

export const ccavRes = functions.https.onRequest(async (req, res) => {
  const result = await processPayment(req);
  return result;
});

需要明确发送响应:

export const ccavRes = functions.https.onRequest(async (req, res) => {
  await processPayment(req);

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

只要有一个条件分支没有发送响应,函数仍可能超时。例如:

if (paymentSucceeded) {
  res.status(200).send("OK");
  return;
}

// 这里没有响应,失败分支会一直等待

应确保每条路径都会结束请求:

if (paymentSucceeded) {
  res.status(200).send("OK");
  return;
}

res.status(400).send("Invalid payment status");

支付回调中的注意事项

处理支付结果时还要注意:

  • 返回 200 前,先完成必须可靠落库的操作,例如更新订单状态。
  • 不要发送响应后再执行重要的数据库写入。响应发出后,运行环境可能暂停或终止实例。
  • 验证支付网关提供的签名、校验值或消息认证码,不能只相信请求中的 transaction status。
  • 根据 transaction ID 做幂等处理。支付平台可能因超时或网络问题,重复发送同一条通知。
  • 如果这是供浏览器跳转的 redirect_url,可以返回 HTML 或使用 res.redirect()。如果是支付平台的服务器通知地址,通常需要尽快返回平台要求的确认内容。
  • 不同支付平台可能对成功响应的正文有特定要求,例如必须返回 OK、success 或指定格式。具体内容应以支付网关的接口文档为准。

如果处理过程可能耗时较长,可以先完成签名验证和必要的幂等记录,再把后续工作交给队列或其他后台任务。但在关键交易状态可靠保存之前,不要仅仅为了避免超时而提前返回成功。

备注:内容仅供参考。